mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 02:52:35 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0645e8b42c | ||
|
|
6504c31cb6 | ||
|
|
53a86bf61e | ||
|
|
8a5ec0ec6f | ||
|
|
b7dbbdf783 | ||
|
|
ec64b24a0c | ||
|
|
2182bf2828 | ||
|
|
2137377f0e | ||
|
|
a9f71f1b6e | ||
|
|
1f9508953f | ||
|
|
39d156b120 | ||
|
|
cb08423712 | ||
|
|
d9a7d8a769 | ||
|
|
5407b7c6d9 | ||
|
|
1d2e4ef747 | ||
|
|
a92049e3e7 | ||
|
|
e8115cea45 | ||
|
|
6a14f7a976 | ||
|
|
c6c770a1ac | ||
|
|
992a8f83b7 | ||
|
|
029022b0f1 | ||
|
|
a06faa6327 | ||
|
|
e7899c6618 | ||
|
|
0f428c5619 | ||
|
|
6d4832a998 | ||
|
|
af70f9ac6d | ||
|
|
54de13abac | ||
|
|
0850bc5147 | ||
|
|
4032145adc | ||
|
|
843d2e3137 | ||
|
|
ad573b9027 | ||
|
|
1c80dfab24 | ||
|
|
eb73980853 | ||
|
|
ac0e54b4c6 | ||
|
|
d719b095dc | ||
|
|
1995e34d6a | ||
|
|
7c14abf2c7 | ||
|
|
f3eb0b5b8c | ||
|
|
7b78d623f7 | ||
|
|
995353e46a | ||
|
|
24110ce90c | ||
|
|
b71c4e613f | ||
|
|
90ffed1f93 | ||
|
|
99a3bf8f62 | ||
|
|
b54c347424 | ||
|
|
c146c9be18 | ||
|
|
6da70c5815 | ||
|
|
6f01687aae | ||
|
|
644f978c18 | ||
|
|
5bd4022fec | ||
|
|
7a6045a3c7 | ||
|
|
73a847ef89 | ||
|
|
ee221d2de0 | ||
|
|
874c96ac1d | ||
|
|
6cbffd50d0 | ||
|
|
b95510ddf4 | ||
|
|
6356b4d516 | ||
|
|
9385fd72c0 | ||
|
|
a2466c8851 | ||
|
|
c3c389d542 | ||
|
|
ec84bef24a | ||
|
|
725184b04e | ||
|
|
290883d94a | ||
|
|
23089fd281 | ||
|
|
792762c42c | ||
|
|
40b840f294 | ||
|
|
6affd45d84 | ||
|
|
47833e0ab9 | ||
|
|
e5c614e1df | ||
|
|
1b1adebe8f | ||
|
|
6d436a3f85 | ||
|
|
994d9bc82b | ||
|
|
9d35ae649e | ||
|
|
cea8ed9702 | ||
|
|
4b6833aec1 | ||
|
|
3894603fef | ||
|
|
c9d668c584 | ||
|
|
083cadc7e9 | ||
|
|
80b74a6c64 | ||
|
|
7c14d11eb9 | ||
|
|
535af525d6 | ||
|
|
09be5cad5b | ||
|
|
1aeca97337 | ||
|
|
dfc3de8b7d | ||
|
|
c2c8fe47f0 | ||
|
|
19ef3de57e | ||
|
|
b345b36620 | ||
|
|
dff4c48ede | ||
|
|
0353b853cb | ||
|
|
b885f33a56 | ||
|
|
71303e04e7 | ||
|
|
63cf15bb9f | ||
|
|
e208575b2d | ||
|
|
13e6c8e97a | ||
|
|
ae21d71e94 | ||
|
|
8e0e22f76b | ||
|
|
5675d5037f | ||
|
|
91f0f1f5ef | ||
|
|
a23c6052c8 | ||
|
|
30d72ee43c | ||
|
|
0e3cc2cf1f | ||
|
|
2448f80375 |
No files matched your search
+23
-19
@@ -1,24 +1,29 @@
|
||||
sudo: false
|
||||
language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
compiler:
|
||||
- clang
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env: COMPILER=clang++-3.6
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6']
|
||||
packages: ['clang-3.6', 'clang-format-3.6', 'zlib1g-dev', 'libbz2-dev', 'libsnappy-dev', 'curl']
|
||||
- os: osx
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6']
|
||||
packages: ['clang-3.6' , 'zlib1g-dev', 'libbz2-dev', 'libsnappy-dev', 'curl']
|
||||
env:
|
||||
# Run all tests before db_block_cache_test (db_test, db_test2)
|
||||
- JOB_NAME=unittests ROCKSDBTESTS_END=db_block_cache_test
|
||||
# Run all tests starting from db_block_cache_test (db_block_cache_test, db_iter_test, ...)
|
||||
- JOB_NAME=unittests ROCKSDBTESTS_START=db_block_cache_test
|
||||
# Run java tests
|
||||
- JOB_NAME=java_test
|
||||
# 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
|
||||
# Download clang-format-diff.py to check source code formatting
|
||||
- pushd /tmp/ && curl -L http://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/clang-format-diff.py -o clang-format-diff.py && chmod +x clang-format-diff.py && popd
|
||||
|
||||
before_script:
|
||||
# Add gflags to include/library paths
|
||||
@@ -26,16 +31,15 @@ before_script:
|
||||
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/tmp/gflags-2.1.2/lib"
|
||||
- export LIBRARY_PATH="$LIBRARY_PATH:/tmp/gflags-2.1.2/lib"
|
||||
- export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/tmp/gflags-2.1.2/include"
|
||||
- if [ -n "${COMPILER}" ]; then CXX=${COMPILER}; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'linux' ]]; then CXX=clang++-3.6; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'osx' ]]; then brew install gflags snappy; fi
|
||||
# Limit the maximum number of open file descriptors to 2000
|
||||
- ulimit -n 2000 || true
|
||||
|
||||
# Lousy hack to disable use and testing of fallocate, which doesn't behave quite
|
||||
# as EnvPosixTest::AllocateTest expects within the Travis OpenVZ environment.
|
||||
script:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'linux' ]]; then OPT=-DTRAVIS CLANG_FORMAT_DIFF=/tmp/clang-format-diff.py make format || true; fi
|
||||
- OPT=-DTRAVIS V=1 make -j4 check && OPT=-DTRAVIS V=1 make clean jclean rocksdbjava jtest && make clean && OPT="-DTRAVIS -DROCKSDB_LITE" V=1 make -j4 static_lib
|
||||
|
||||
- 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
|
||||
notifications:
|
||||
email:
|
||||
- leveldb@fb.com
|
||||
|
||||
+8
-2
@@ -82,7 +82,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /MD")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
|
||||
|
||||
add_definitions(-DWIN32 -DOS_WIN -D_MBCS -DWIN64)
|
||||
add_definitions(-DWIN32 -DOS_WIN -D_MBCS -DWIN64 -DNOMINMAX)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
@@ -90,7 +90,7 @@ include_directories(${PROJECT_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src)
|
||||
|
||||
set(ROCKSDB_LIBS rocksdblib${ARTIFACT_SUFFIX})
|
||||
set(THIRDPARTY_LIBS ${THIRDPARTY_LIBS} gtest)
|
||||
set(SYSTEM_LIBS Shlwapi.lib Rpcrt4.lib)
|
||||
set(SYSTEM_LIBS ${SYSTEM_LIBS} Shlwapi.lib Rpcrt4.lib)
|
||||
|
||||
set(LIBS ${ROCKSDB_LIBS} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
|
||||
|
||||
@@ -156,6 +156,7 @@ set(SOURCES
|
||||
port/win/env_win.cc
|
||||
port/win/port_win.cc
|
||||
port/win/win_logger.cc
|
||||
port/win/xpress_win.cc
|
||||
table/adaptive_table_factory.cc
|
||||
table/block.cc
|
||||
table/block_based_filter_block.cc
|
||||
@@ -310,6 +311,7 @@ set(APPS
|
||||
tools/db_stress.cc
|
||||
tools/write_stress.cc
|
||||
tools/db_repl_stress.cc
|
||||
tools/ldb.cc
|
||||
tools/sst_dump.cc
|
||||
tools/dump/rocksdb_dump.cc
|
||||
tools/dump/rocksdb_undump.cc
|
||||
@@ -341,6 +343,9 @@ set(TESTS
|
||||
db/db_test.cc
|
||||
db/db_test2.cc
|
||||
db/db_block_cache_test.cc
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_iterator_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/db_universal_compaction_test.cc
|
||||
db/db_wal_test.cc
|
||||
db/dbformat_test.cc
|
||||
@@ -399,6 +404,7 @@ set(TESTS
|
||||
util/iostats_context_test.cc
|
||||
util/memenv_test.cc
|
||||
util/mock_env_test.cc
|
||||
util/options_settable_test.cc
|
||||
util/options_test.cc
|
||||
util/rate_limiter_test.cc
|
||||
util/slice_transform_test.cc
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
## Unreleased
|
||||
* options.write_buffer_size changes from 4MB to 64MB
|
||||
* options.target_file_size_base changes from 2MB to 64MB
|
||||
* options.max_bytes_for_level_base changes from 10MB to 256MB
|
||||
* options.soft_pending_compaction_bytes_limit changes from 0 (disabled) to 64GB
|
||||
* options.hard_pending_compaction_bytes_limit changes from 0 (disabled) to 256GB
|
||||
* table_cache_numshardbits changes from 4 to 6
|
||||
* max_file_opening_threads changes from 1 to 16
|
||||
# RocksDB default options change log
|
||||
## 4.8.0 (5/2/2016)
|
||||
* options.max_open_files changes from 5000 to -1. It improves performance, but users need to set file descriptor limit to be large enough and watch memory usage for index and bloom filters.
|
||||
* options.base_background_compactions changes from max_background_compactions to 1. When users set higher max_background_compactions but the write throughput is not high, the writes are less spiky to disks.
|
||||
* options.wal_recovery_mode changes from kTolerateCorruptedTailRecords to kPointInTimeRecovery. Avoid some false positive when file system or hardware reorder the writes for file data and metadata.
|
||||
|
||||
## 4.7.0 (4/8/2016)
|
||||
* options.write_buffer_size changes from 4MB to 64MB.
|
||||
* options.target_file_size_base changes from 2MB to 64MB.
|
||||
* options.max_bytes_for_level_base changes from 10MB to 256MB.
|
||||
* options.soft_pending_compaction_bytes_limit changes from 0 (disabled) to 64GB.
|
||||
* options.hard_pending_compaction_bytes_limit changes from 0 (disabled) to 256GB.
|
||||
* table_cache_numshardbits changes from 4 to 6.
|
||||
* max_file_opening_threads changes from 1 to 16.
|
||||
+11
-1
@@ -1,6 +1,16 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 4.8.0 (5/2/2016)
|
||||
### Public API Change
|
||||
* Allow preset compression dictionary for improved compression of block-based tables. This is supported for zlib, zstd, and lz4. The compression dictionary's size is configurable via CompressionOptions::max_dict_bytes.
|
||||
* Delete deprecated classes for creating backups (BackupableDB) and restoring from backups (RestoreBackupableDB). Now, BackupEngine should be used for creating backups, and BackupEngineReadOnly should be used for restorations. For more details, see https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F
|
||||
* Expose estimate of per-level compression ratio via DB property: "rocksdb.compression-ratio-at-levelN".
|
||||
* Added EventListener::OnTableFileCreationStarted. EventListener::OnTableFileCreated will be called on failure case. User can check creation status via TableFileCreationInfo::status.
|
||||
### New Features
|
||||
* Add ReadOptions::readahead_size. If non-zero, NewIterator will create a new table reader which performs reads of the given size.
|
||||
|
||||
## 4.7.0 (4/8/2016)
|
||||
### Public API Change
|
||||
* rename options compaction_measure_io_stats to report_bg_io_stats and include flush too.
|
||||
* Change some default options. Now default options will optimize for server-workloads. Also enable slowdown and full stop triggers for pending compaction bytes. These changes may cause sub-optimal performance or significant increase of resource usage. To avoid these risks, users can open existing RocksDB with options extracted from RocksDB option files. See https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File for how to use RocksDB option files. Or you can call Options.OldDefaults() to recover old defaults. DEFAULT_OPTIONS_HISTORY.md will track change history of default options.
|
||||
|
||||
## 4.6.0 (3/10/2016)
|
||||
|
||||
@@ -15,7 +15,9 @@ ARFLAGS = rs
|
||||
|
||||
# Transform parallel LOG output into something more readable.
|
||||
perl_command = perl -n \
|
||||
-e '@a=split("\t",$$_,-1); $$t=$$a[8]; $$t =~ s,^\./,,;' \
|
||||
-e '@a=split("\t",$$_,-1); $$t=$$a[8];' \
|
||||
-e '$$t =~ /.*if\s\[\[\s"(.*?\.[\w\/]+)/ and $$t=$$1;' \
|
||||
-e '$$t =~ s,^\./,,;' \
|
||||
-e '$$t =~ s, >.*,,; chomp $$t;' \
|
||||
-e '$$t =~ /.*--gtest_filter=(.*?\.[\w\/]+)/ and $$t=$$1;' \
|
||||
-e 'printf "%7.3f %s %s\n", $$a[3], $$a[6] == 0 ? "PASS" : "FAIL", $$t'
|
||||
@@ -169,9 +171,7 @@ ifdef COMPILE_WITH_TSAN
|
||||
PLATFORM_CXXFLAGS += -fsanitize=thread -fPIC -DROCKSDB_TSAN_RUN
|
||||
# Turn off -pg when enabling TSAN testing, because that induces
|
||||
# a link failure. TODO: find the root cause
|
||||
pg =
|
||||
else
|
||||
pg = -pg
|
||||
PROFILING_FLAGS =
|
||||
endif
|
||||
|
||||
# USAN doesn't work well with jemalloc. If we're compiling with USAN, we should use regular malloc.
|
||||
@@ -182,8 +182,11 @@ ifdef COMPILE_WITH_UBSAN
|
||||
PLATFORM_CXXFLAGS += -fsanitize=undefined
|
||||
endif
|
||||
|
||||
|
||||
ifndef DISABLE_JEMALLOC
|
||||
ifdef JEMALLOC
|
||||
PLATFORM_CXXFLAGS += "-DROCKSDB_JEMALLOC"
|
||||
PLATFORM_CCFLAGS += "-DROCKSDB_JEMALLOC"
|
||||
endif
|
||||
EXEC_LDFLAGS := $(JEMALLOC_LIB) $(EXEC_LDFLAGS)
|
||||
PLATFORM_CXXFLAGS += $(JEMALLOC_INCLUDE)
|
||||
PLATFORM_CCFLAGS += $(JEMALLOC_INCLUDE)
|
||||
@@ -256,12 +259,15 @@ TESTS = \
|
||||
db_test \
|
||||
db_test2 \
|
||||
db_block_cache_test \
|
||||
db_bloom_filter_test \
|
||||
db_iter_test \
|
||||
db_log_iter_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_dynamic_level_test \
|
||||
db_inplace_update_test \
|
||||
db_iterator_test \
|
||||
db_sst_test \
|
||||
db_tailing_iter_test \
|
||||
db_universal_compaction_test \
|
||||
db_wal_test \
|
||||
@@ -330,6 +336,7 @@ TESTS = \
|
||||
rate_limiter_test \
|
||||
delete_scheduler_test \
|
||||
options_test \
|
||||
options_settable_test \
|
||||
options_util_test \
|
||||
event_logger_test \
|
||||
cuckoo_table_builder_test \
|
||||
@@ -353,7 +360,26 @@ TESTS = \
|
||||
ldb_cmd_test \
|
||||
iostats_context_test
|
||||
|
||||
SUBSET := $(shell echo $(TESTS) |sed s/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/)
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
compact_on_deletion_collector_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_test \
|
||||
db_universal_compaction_test \
|
||||
fault_injection_test \
|
||||
inlineskiplist_test \
|
||||
manual_compaction_test \
|
||||
table_test
|
||||
|
||||
SUBSET := $(TESTS)
|
||||
ifdef ROCKSDBTESTS_START
|
||||
SUBSET := $(shell echo $(SUBSET) | sed 's/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/')
|
||||
endif
|
||||
|
||||
ifdef ROCKSDBTESTS_END
|
||||
SUBSET := $(shell echo $(SUBSET) | sed 's/$(ROCKSDBTESTS_END).*//')
|
||||
endif
|
||||
|
||||
TOOLS = \
|
||||
sst_dump \
|
||||
@@ -454,7 +480,26 @@ coverage:
|
||||
# Delete intermediate files
|
||||
find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
|
||||
# Extract the names of its tests by running db_test with --gtest_list_tests.
|
||||
ifneq (,$(filter check parallel_check,$(MAKECMDGOALS)),)
|
||||
# Use /dev/shm if it has the sticky bit set (otherwise, /tmp),
|
||||
# and create a randomly-named rocksdb.XXXX directory therein.
|
||||
# We'll use that directory in the "make check" rules.
|
||||
ifeq ($(TMPD),)
|
||||
TMPD := $(shell f=/dev/shm; test -k $$f || f=/tmp; \
|
||||
perl -le 'use File::Temp "tempdir";' \
|
||||
-e 'print tempdir("'$$f'/rocksdb.XXXX", CLEANUP => 0)')
|
||||
endif
|
||||
endif
|
||||
|
||||
# Run all tests in parallel, accumulating per-test logs in t/log-*.
|
||||
#
|
||||
# Each t/run-* file is a tiny generated bourne shell script that invokes one of
|
||||
# sub-tests. Why use a file for this? Because that makes the invocation of
|
||||
# parallel below simpler, which in turn makes the parsing of parallel's
|
||||
# LOG simpler (the latter is for live monitoring as parallel
|
||||
# tests run).
|
||||
#
|
||||
# Test names are extracted by running tests with --gtest_list_tests.
|
||||
# This filter removes the "#"-introduced comments, and expands to
|
||||
# fully-qualified names by changing input like this:
|
||||
#
|
||||
@@ -472,52 +517,33 @@ coverage:
|
||||
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/0
|
||||
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/1
|
||||
#
|
||||
test_names = \
|
||||
./db_test --gtest_list_tests \
|
||||
| perl -n \
|
||||
-e 's/ *\#.*//;' \
|
||||
-e '/^(\s*)(\S+)/; !$$1 and do {$$p=$$2; break};' \
|
||||
-e 'print qq! $$p$$2!'
|
||||
|
||||
ifneq (,$(filter check parallel_check,$(MAKECMDGOALS)),)
|
||||
# Use /dev/shm if it has the sticky bit set (otherwise, /tmp),
|
||||
# and create a randomly-named rocksdb.XXXX directory therein.
|
||||
# We'll use that directory in the "make check" rules.
|
||||
ifeq ($(TMPD),)
|
||||
TMPD := $(shell f=/dev/shm; test -k $$f || f=/tmp; \
|
||||
perl -le 'use File::Temp "tempdir";' \
|
||||
-e 'print tempdir("'$$f'/rocksdb.XXXX", CLEANUP => 0)')
|
||||
endif
|
||||
endif
|
||||
parallel_tests = $(patsubst %,parallel_%,$(PARALLEL_TEST))
|
||||
.PHONY: gen_parallel_tests $(parallel_tests)
|
||||
$(parallel_tests): $(PARALLEL_TEST)
|
||||
$(AM_V_at)TEST_BINARY=$(patsubst parallel_%,%,$@); \
|
||||
TEST_NAMES=` \
|
||||
./$$TEST_BINARY --gtest_list_tests \
|
||||
| perl -n \
|
||||
-e 's/ *\#.*//;' \
|
||||
-e '/^(\s*)(\S+)/; !$$1 and do {$$p=$$2; break};' \
|
||||
-e 'print qq! $$p$$2!'`; \
|
||||
for TEST_NAME in $$TEST_NAMES; do \
|
||||
TEST_SCRIPT=t/run-$$TEST_BINARY-$${TEST_NAME//\//-}; \
|
||||
echo " GEN " $$TEST_SCRIPT; \
|
||||
printf '%s\n' \
|
||||
'#!/bin/sh' \
|
||||
"d=\$(TMPD)$$TEST_SCRIPT" \
|
||||
'mkdir -p $$d' \
|
||||
"TEST_TMPDIR=\$$d ./$$TEST_BINARY --gtest_filter=$$TEST_NAME" \
|
||||
> $$TEST_SCRIPT; \
|
||||
chmod a=rx $$TEST_SCRIPT; \
|
||||
done
|
||||
|
||||
ifneq ($(T),)
|
||||
|
||||
# Run all tests in parallel, accumulating per-test logs in t/log-*.
|
||||
|
||||
# t_sanitized is each $(T) with "-" in place of each "/".
|
||||
t_sanitized = $(subst /,-,$(T))
|
||||
|
||||
# t_run is each sanitized name with a leading "t/".
|
||||
t_run = $(patsubst %,t/%,$(t_sanitized))
|
||||
|
||||
# Each t_run file is a tiny generated bourne shell script
|
||||
# that invokes one of db_tests's sub-tests. Why use a file
|
||||
# for this? Because that makes the invocation of parallel
|
||||
# below simpler, which in turn makes the parsing of parallel's
|
||||
# LOG simpler (the latter is for live monitoring as parallel
|
||||
# tests run).
|
||||
filter = --gtest_filter=$(subst -,/,$(@F))
|
||||
$(t_run): Makefile db_test
|
||||
$(AM_V_GEN)mkdir -p t
|
||||
$(AM_V_at)rm -f $@ $@-t
|
||||
$(AM_V_at)printf '%s\n' \
|
||||
'#!/bin/sh' \
|
||||
'd=$(TMPD)/$(@F)' \
|
||||
'mkdir -p $$d' \
|
||||
'TEST_TMPDIR=$$d ./db_test $(filter)' \
|
||||
> $@-t
|
||||
$(AM_V_at)chmod a=rx $@-t
|
||||
$(AM_V_at)mv $@-t $@
|
||||
gen_parallel_tests:
|
||||
$(AM_V_at)mkdir -p t
|
||||
$(AM_V_at)rm -f t/run-*
|
||||
$(MAKE) $(parallel_tests)
|
||||
|
||||
# Reorder input lines (which are one per test) so that the
|
||||
# longest-running tests appear first in the output.
|
||||
@@ -536,7 +562,7 @@ $(t_run): Makefile db_test
|
||||
# 107.816 PASS t/DBTest.EncodeDecompressedBlockSizeTest
|
||||
#
|
||||
slow_test_regexp = \
|
||||
^t/DBTest\.(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$
|
||||
^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$
|
||||
prioritize_long_running_tests = \
|
||||
perl -pe 's,($(slow_test_regexp)),100 $$1,' \
|
||||
| sort -k1,1gr \
|
||||
@@ -547,26 +573,43 @@ prioritize_long_running_tests = \
|
||||
# Run with "make J=200% check" to run two parallel jobs per core.
|
||||
# The default is to run one job per core (J=100%).
|
||||
# See "man parallel" for its "-j ..." option.
|
||||
J = 100%
|
||||
J ?= 100%
|
||||
|
||||
# Use this regexp to select the subset of tests whose names match.
|
||||
tests-regexp = .
|
||||
|
||||
t_run = $(wildcard t/run-*)
|
||||
.PHONY: check_0
|
||||
check_0: $(t_run)
|
||||
check_0:
|
||||
$(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \
|
||||
printf '%s\n' '' \
|
||||
'To monitor subtest <duration,pass/fail,name>,' \
|
||||
' run "make watch-log" in a separate window' ''; \
|
||||
test -t 1 && eta=--eta || eta=; \
|
||||
{ \
|
||||
printf './%s\n' $(filter-out $(PARALLEL_TEST),$(TESTS)); \
|
||||
printf '%s\n' $(t_run); \
|
||||
} \
|
||||
| $(prioritize_long_running_tests) \
|
||||
| grep -E '$(tests-regexp)' \
|
||||
| parallel -j$(J) --joblog=LOG $$eta --gnu '{} >& t/log-{/}'
|
||||
|
||||
.PHONY: valgrind_check_0
|
||||
valgrind_check_0:
|
||||
$(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \
|
||||
printf '%s\n' '' \
|
||||
'To monitor subtest <duration,pass/fail,name>,' \
|
||||
' run "make watch-log" in a separate window' ''; \
|
||||
test -t 1 && eta=--eta || eta=; \
|
||||
{ \
|
||||
printf './%s\n' $(filter-out db_test, $(TESTS)); \
|
||||
printf './%s\n' $(filter-out $(PARALLEL_TEST) %skiplist_test options_settable_test, $(TESTS)); \
|
||||
printf '%s\n' $(t_run); \
|
||||
} \
|
||||
| $(prioritize_long_running_tests) \
|
||||
| grep -E '$(tests-regexp)' \
|
||||
| parallel -j$(J) --joblog=LOG $$eta --gnu '{} >& t/log-{/}'
|
||||
endif
|
||||
| parallel -j$(J) --joblog=LOG $$eta --gnu \
|
||||
'if [[ "{}" == "./"* ]] ; then $(DRIVER) {} >& t/valgrind_log-{/}; ' \
|
||||
'else {} >& t/valgrind_log-{/}; fi'
|
||||
|
||||
CLEAN_FILES += t LOG $(TMPD)
|
||||
|
||||
@@ -583,11 +626,11 @@ watch-log:
|
||||
# If J != 1 and GNU parallel is installed, run the tests in parallel,
|
||||
# via the check_0 rule above. Otherwise, run them sequentially.
|
||||
check: all
|
||||
$(MAKE) gen_parallel_tests
|
||||
$(AM_V_GEN)if test "$(J)" != 1 \
|
||||
&& (parallel --gnu --help 2>/dev/null) | \
|
||||
grep -q 'GNU Parallel'; \
|
||||
then \
|
||||
t=$$($(test_names)); \
|
||||
$(MAKE) T="$$t" TMPD=$(TMPD) check_0; \
|
||||
else \
|
||||
for t in $(TESTS); do \
|
||||
@@ -637,13 +680,22 @@ ubsan_crash_test:
|
||||
$(MAKE) clean
|
||||
|
||||
valgrind_check: $(TESTS)
|
||||
for t in $(filter-out %skiplist_test,$(TESTS)); do \
|
||||
$(VALGRIND_VER) $(VALGRIND_OPTS) ./$$t; \
|
||||
ret_code=$$?; \
|
||||
if [ $$ret_code -ne 0 ]; then \
|
||||
exit $$ret_code; \
|
||||
fi; \
|
||||
done
|
||||
$(MAKE) gen_parallel_tests
|
||||
$(AM_V_GEN)if test "$(J)" != 1 \
|
||||
&& (parallel --gnu --help 2>/dev/null) | \
|
||||
grep -q 'GNU Parallel'; \
|
||||
then \
|
||||
$(MAKE) TMPD=$(TMPD) \
|
||||
DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" valgrind_check_0; \
|
||||
else \
|
||||
for t in $(filter-out %skiplist_test options_settable_test,$(TESTS)); do \
|
||||
$(VALGRIND_VER) $(VALGRIND_OPTS) ./$$t; \
|
||||
ret_code=$$?; \
|
||||
if [ $$ret_code -ne 0 ]; then \
|
||||
exit $$ret_code; \
|
||||
fi; \
|
||||
done; \
|
||||
fi
|
||||
|
||||
|
||||
ifneq ($(PAR_TEST),)
|
||||
@@ -667,6 +719,13 @@ parloop:
|
||||
exit $$ret_bad;
|
||||
endif
|
||||
|
||||
test_names = \
|
||||
./db_test --gtest_list_tests \
|
||||
| perl -n \
|
||||
-e 's/ *\#.*//;' \
|
||||
-e '/^(\s*)(\S+)/; !$$1 and do {$$p=$$2; break};' \
|
||||
-e 'print qq! $$p$$2!'
|
||||
|
||||
parallel_check: $(TESTS)
|
||||
$(AM_V_GEN)if test "$(J)" > 1 \
|
||||
&& (parallel --gnu --help 2>/dev/null) | \
|
||||
@@ -817,6 +876,9 @@ db_test2: db/db_test2.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_block_cache_test: db/db_block_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_bloom_filter_test: db/db_bloom_filter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_log_iter_test: db/db_log_iter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -832,6 +894,12 @@ db_dynamic_level_test: db/db_dynamic_level_test.o db/db_test_util.o $(LIBOBJECTS
|
||||
db_inplace_update_test: db/db_inplace_update_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_iterator_test: db/db_iterator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_sst_test: db/db_sst_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_tailing_iter_test: db/db_tailing_iter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -851,7 +919,7 @@ db_table_properties_test: db/db_table_properties_test.o db/db_test_util.o $(LIBO
|
||||
$(AM_LINK)
|
||||
|
||||
log_write_bench: util/log_write_bench.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK) $(pg)
|
||||
$(AM_LINK) $(PROFILING_FLAGS)
|
||||
|
||||
plain_table_db_test: db/plain_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
@@ -860,7 +928,7 @@ comparator_db_test: db/comparator_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
table_reader_bench: table/table_reader_bench.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK) $(pg)
|
||||
$(AM_LINK) $(PROFILING_FLAGS)
|
||||
|
||||
perf_context_test: db/perf_context_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
@@ -1024,6 +1092,9 @@ compact_files_test: db/compact_files_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
options_test: util/options_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
options_settable_test: util/options_settable_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
options_util_test: utilities/options/options_util_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ At Facebook, we use RocksDB as a backend for many different stateful services. W
|
||||
## LinkedIn
|
||||
Two different use cases at Linkedin are using RocksDB as a storage engine:
|
||||
|
||||
1. LinkedIn's follow feed for storing user's activities
|
||||
1. LinkedIn's follow feed for storing user's activities. Check out the blog post: https://engineering.linkedin.com/blog/2016/03/followfeed--linkedin-s-feed-made-faster-and-smarter
|
||||
2. Apache Samza, open source framework for stream processing
|
||||
|
||||
Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasundaram: http://www.youtube.com/watch?v=plqVp_OnSzg
|
||||
@@ -55,4 +55,4 @@ Pinterest's Object Retrieval System uses RocksDB for storage: https://www.youtub
|
||||
|
||||
## quasardb
|
||||
[quasardb](https://www.quasardb.net) is a high-performance, distributed, transactional key-value database that integrates well with in-memory analytics engines such as Apache Spark.
|
||||
quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
@@ -92,12 +92,13 @@ class FacebookArcanistConfiguration extends ArcanistConfiguration {
|
||||
// Run the actual command
|
||||
$this->updateTest($diffID, $test);
|
||||
$cmd = $this->updateTestCommand($diffID, $test, "running") . ";"
|
||||
. "(./build_tools/precommit_checker.py " . $test
|
||||
. "&& "
|
||||
. "./build_tools/precommit_checker.py " . $test
|
||||
. "; exit_code=$?; ([[ \$exit_code -eq 0 ]] &&"
|
||||
. $this->updateTestCommand($diffID, $test, "pass") . ")"
|
||||
. "|| " . $this->updateTestCommand($diffID, $test, "fail")
|
||||
. "||" . $this->updateTestCommand($diffID, $test, "fail")
|
||||
. "; cat /tmp/precommit-check.log"
|
||||
. "; for f in `ls t/log-*`; do echo \$f; cat \$f; done";
|
||||
. "; for f in `ls t/log-*`; do echo \$f; cat \$f; done;"
|
||||
. "[[ \$exit_code -eq 0 ]]";
|
||||
|
||||
$run_test = array(
|
||||
"name" => "Run " . $test,
|
||||
|
||||
Vendored
+1
-1
@@ -2714,7 +2714,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
||||
|
||||
# Look for < that is not surrounded by spaces. This is only
|
||||
# triggered if both sides are missing spaces, even though
|
||||
# technically should should flag if at least one side is missing a
|
||||
# technically should flag if at least one side is missing a
|
||||
# space. This is done to avoid some false positives with shifts.
|
||||
match = Search(r'[^\s<]<([^\s=<].*)', reduced_line)
|
||||
if (match and
|
||||
|
||||
@@ -299,13 +299,14 @@ EOF
|
||||
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
2>/dev/null; then
|
||||
2>/dev/null; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ljemalloc"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ljemalloc"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# jemalloc is not available. Let's try tcmalloc
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null \
|
||||
-ltcmalloc 2>/dev/null; then
|
||||
-ltcmalloc 2>/dev/null; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltcmalloc"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltcmalloc"
|
||||
fi
|
||||
@@ -322,6 +323,55 @@ EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
fi
|
||||
|
||||
# Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <pthread.h>
|
||||
int main() {
|
||||
int x = PTHREAD_MUTEX_ADAPTIVE_NP;
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_PTHREAD_ADAPTIVE_MUTEX"
|
||||
fi
|
||||
|
||||
# Test whether backtrace is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
backtrace_symbols(frames, backtrace(frames, 1));
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
else
|
||||
# Test whether execinfo library is installed
|
||||
$CXX $CFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
backtrace_symbols(frames, backtrace(frames, 1));
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lexecinfo"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lexecinfo"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test if -pg is supported
|
||||
$CXX $CFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
PROFILING_FLAGS=-pg
|
||||
fi
|
||||
fi
|
||||
|
||||
# TODO(tec): Fix -Wshorten-64-to-32 errors on FreeBSD and enable the warning.
|
||||
@@ -402,3 +452,7 @@ echo "ROCKSDB_MINOR=$ROCKSDB_MINOR" >> "$OUTPUT"
|
||||
echo "ROCKSDB_PATCH=$ROCKSDB_PATCH" >> "$OUTPUT"
|
||||
echo "CLANG_SCAN_BUILD=$CLANG_SCAN_BUILD" >> "$OUTPUT"
|
||||
echo "CLANG_ANALYZER=$CLANG_ANALYZER" >> "$OUTPUT"
|
||||
echo "PROFILING_FLAGS=$PROFILING_FLAGS" >> "$OUTPUT"
|
||||
if test -n "$JEMALLOC"; then
|
||||
echo "JEMALLOC=1" >> "$OUTPUT"
|
||||
fi
|
||||
@@ -0,0 +1,164 @@
|
||||
# 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.
|
||||
|
||||
'''Filter for error messages in test output:
|
||||
- Receives merged stdout/stderr from test on stdin
|
||||
- Finds patterns of known error messages for test name (first argument)
|
||||
- Prints those error messages to stdout
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class ErrorParserBase(object):
|
||||
def parse_error(self, line):
|
||||
'''Parses a line of test output. If it contains an error, returns a
|
||||
formatted message describing the error; otherwise, returns None.
|
||||
Subclasses must override this method.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class GTestErrorParser(ErrorParserBase):
|
||||
'''A parser that remembers the last test that began running so it can print
|
||||
that test's name upon detecting failure.
|
||||
'''
|
||||
_GTEST_NAME_PATTERN = re.compile(r'\[ RUN \] (\S+)$')
|
||||
# format: '<filename or "unknown file">:<line #>: Failure'
|
||||
_GTEST_FAIL_PATTERN = re.compile(r'(unknown file|\S+:\d+): Failure$')
|
||||
|
||||
def __init__(self):
|
||||
self._last_gtest_name = 'Unknown test'
|
||||
|
||||
def parse_error(self, line):
|
||||
gtest_name_match = self._GTEST_NAME_PATTERN.match(line)
|
||||
if gtest_name_match:
|
||||
self._last_gtest_name = gtest_name_match.group(1)
|
||||
return None
|
||||
gtest_fail_match = self._GTEST_FAIL_PATTERN.match(line)
|
||||
if gtest_fail_match:
|
||||
return '%s failed: %s' % (
|
||||
self._last_gtest_name, gtest_fail_match.group(1))
|
||||
return None
|
||||
|
||||
|
||||
class MatchErrorParser(ErrorParserBase):
|
||||
'''A simple parser that returns the whole line if it matches the pattern.
|
||||
'''
|
||||
def __init__(self, pattern):
|
||||
self._pattern = re.compile(pattern)
|
||||
|
||||
def parse_error(self, line):
|
||||
if self._pattern.match(line):
|
||||
return line
|
||||
return None
|
||||
|
||||
|
||||
class CompilerErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# format: '<filename>:<line #>:<column #>: error: <error msg>'
|
||||
super(CompilerErrorParser, self).__init__(r'\S+:\d+:\d+: error:')
|
||||
|
||||
|
||||
class ScanBuildErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(ScanBuildErrorParser, self).__init__(
|
||||
r'scan-build: \d+ bugs found.$')
|
||||
|
||||
|
||||
class DbCrashErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(DbCrashErrorParser, self).__init__(r'\*\*\*.*\^$|TEST FAILED.')
|
||||
|
||||
|
||||
class WriteStressErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(WriteStressErrorParser, self).__init__(
|
||||
r'ERROR: write_stress died with exitcode=\d+')
|
||||
|
||||
|
||||
class AsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(AsanErrorParser, self).__init__(
|
||||
r'==\d+==ERROR: AddressSanitizer:')
|
||||
|
||||
|
||||
class UbsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# format: '<filename>:<line #>:<column #>: runtime error: <error msg>'
|
||||
super(UbsanErrorParser, self).__init__(r'\S+:\d+:\d+: runtime error:')
|
||||
|
||||
|
||||
class ValgrindErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# just grab the summary, valgrind doesn't clearly distinguish errors
|
||||
# from other log messages.
|
||||
super(ValgrindErrorParser, self).__init__(r'==\d+== ERROR SUMMARY:')
|
||||
|
||||
|
||||
class CompatErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(CompatErrorParser, self).__init__(r'==== .*[Ee]rror.* ====$')
|
||||
|
||||
|
||||
class TsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(TsanErrorParser, self).__init__(r'WARNING: ThreadSanitizer:')
|
||||
|
||||
|
||||
_TEST_NAME_TO_PARSERS = {
|
||||
'punit': [CompilerErrorParser, GTestErrorParser],
|
||||
'unit': [CompilerErrorParser, GTestErrorParser],
|
||||
'unit_481': [CompilerErrorParser, GTestErrorParser],
|
||||
'clang_unit': [CompilerErrorParser, GTestErrorParser],
|
||||
'clang_analyze': [CompilerErrorParser, ScanBuildErrorParser],
|
||||
'code_cov': [CompilerErrorParser, GTestErrorParser],
|
||||
'unity': [CompilerErrorParser, GTestErrorParser],
|
||||
'lite': [CompilerErrorParser],
|
||||
'lite_test': [CompilerErrorParser, GTestErrorParser],
|
||||
'stress_crash': [CompilerErrorParser, DbCrashErrorParser],
|
||||
'write_stress': [CompilerErrorParser, WriteStressErrorParser],
|
||||
'asan': [CompilerErrorParser, GTestErrorParser, AsanErrorParser],
|
||||
'asan_crash': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser],
|
||||
'ubsan': [CompilerErrorParser, GTestErrorParser, UbsanErrorParser],
|
||||
'ubsan_crash': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser],
|
||||
'valgrind': [CompilerErrorParser, GTestErrorParser, ValgrindErrorParser],
|
||||
'tsan': [CompilerErrorParser, GTestErrorParser, TsanErrorParser],
|
||||
'format_compatible': [CompilerErrorParser, CompatErrorParser],
|
||||
'run_format_compatible': [CompilerErrorParser, CompatErrorParser],
|
||||
'no_compression': [CompilerErrorParser, GTestErrorParser],
|
||||
'run_no_compression': [CompilerErrorParser, GTestErrorParser],
|
||||
'regression': [CompilerErrorParser],
|
||||
'run_regression': [CompilerErrorParser],
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
return 'Usage: %s <test name>' % sys.argv[0]
|
||||
test_name = sys.argv[1]
|
||||
if test_name not in _TEST_NAME_TO_PARSERS:
|
||||
return 'Unknown test name: %s' % test_name
|
||||
|
||||
error_parsers = []
|
||||
for parser_cls in _TEST_NAME_TO_PARSERS[test_name]:
|
||||
error_parsers.append(parser_cls())
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
for error_parser in error_parsers:
|
||||
error_msg = error_parser.parse_error(line)
|
||||
if error_msg is not None:
|
||||
print(error_msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -18,7 +18,7 @@ LIBGCC_LIBS=" -L $LIBGCC_BASE/libs"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIB_BASE/lib"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
@@ -97,6 +97,7 @@ if [ -z "$USE_CLANG" ]; then
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/*/include"
|
||||
|
||||
@@ -66,6 +66,7 @@ if [ -z "$USE_CLANG" ]; then
|
||||
CFLAGS="-B$BINUTILS/gold -m64 -mtune=generic"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
|
||||
@@ -69,7 +69,9 @@ LITE="OPT=-DROCKSDB_LITE"
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
UBSAN="COMPILE_WITH_UBSAN=1"
|
||||
DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
|
||||
PARSER="'parser':'egrep \'Failure|^#|Abort|Expected|Actual|GoogleTestFailure|^==\''"
|
||||
HTTP_PROXY="https_proxy=http://fwdproxy.29.prn1:8080 http_proxy=http://fwdproxy.29.prn1:8080 ftp_proxy=http://fwdproxy.29.prn1:8080"
|
||||
SETUP_JAVA_ENV="export $HTTP_PROXY; export JAVA_HOME=/usr/local/jdk-7u10-64/; export PATH=\$PATH:\$JAVA_HOME/bin"
|
||||
PARSER="'parser':'python build_tools/error_filter.py $1'"
|
||||
|
||||
ARTIFACTS=" 'artifacts': [
|
||||
{
|
||||
@@ -635,6 +637,27 @@ REGRESSION_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB Java build
|
||||
#
|
||||
JAVA_BUILD_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Java Build',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB for Java',
|
||||
'shell':'$SETUP_JAVA_ENV; $SHM make rocksdbjava',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
|
||||
case $1 in
|
||||
punit)
|
||||
echo $PARALLEL_UNIT_TEST_COMMANDS
|
||||
@@ -708,6 +731,9 @@ case $1 in
|
||||
run_regression)
|
||||
run_regression
|
||||
;;
|
||||
java_build)
|
||||
echo $JAVA_BUILD_TEST_COMMANDS
|
||||
;;
|
||||
*)
|
||||
echo "Invalid determinator command"
|
||||
;;
|
||||
|
||||
@@ -458,7 +458,16 @@ TEST_F(AutoRollLoggerTest, LogHeaderTest) {
|
||||
TEST_F(AutoRollLoggerTest, LogFileExistence) {
|
||||
rocksdb::DB* db;
|
||||
rocksdb::Options options;
|
||||
#ifdef OS_WIN
|
||||
// Replace all slashes in the path so windows CompSpec does not
|
||||
// become confused
|
||||
std::string testDir(kTestDir);
|
||||
std::replace_if(testDir.begin(), testDir.end(),
|
||||
[](char ch) { return ch == '/'; }, '\\');
|
||||
std::string deleteCmd = "if exist " + testDir + " rd /s /q " + testDir;
|
||||
#else
|
||||
string deleteCmd = "rm -rf " + kTestDir;
|
||||
#endif
|
||||
ASSERT_EQ(system(deleteCmd.c_str()), 0);
|
||||
options.max_log_file_size = 100 * 1024 * 1024;
|
||||
options.create_if_missing = true;
|
||||
|
||||
+25
-5
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "db/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
@@ -43,14 +44,15 @@ TableBuilder* NewTableBuilder(
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, const bool skip_filters) {
|
||||
const CompressionOptions& compression_opts,
|
||||
const std::string* compression_dict, const bool skip_filters) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
return ioptions.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(ioptions, internal_comparator,
|
||||
int_tbl_prop_collector_factories, compression_type,
|
||||
compression_opts, skip_filters,
|
||||
compression_opts, compression_dict, skip_filters,
|
||||
column_family_name),
|
||||
column_family_id, file);
|
||||
}
|
||||
@@ -67,7 +69,8 @@ Status BuildTable(
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, const Env::IOPriority io_priority,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
@@ -80,6 +83,12 @@ Status BuildTable(
|
||||
|
||||
std::string fname = TableFileName(ioptions.db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
#ifndef ROCKSDB_LITE
|
||||
EventHelpers::NotifyTableFileCreationStarted(
|
||||
ioptions.listeners, dbname, column_family_name, fname, job_id, reason);
|
||||
#endif // !ROCKSDB_LITE
|
||||
TableProperties tp;
|
||||
|
||||
if (iter->Valid()) {
|
||||
TableBuilder* builder;
|
||||
unique_ptr<WritableFileWriter> file_writer;
|
||||
@@ -87,6 +96,9 @@ Status BuildTable(
|
||||
unique_ptr<WritableFile> file;
|
||||
s = NewWritableFile(env, fname, &file, env_options);
|
||||
if (!s.ok()) {
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
return s;
|
||||
}
|
||||
file->SetIOPriority(io_priority);
|
||||
@@ -134,11 +146,13 @@ Status BuildTable(
|
||||
}
|
||||
|
||||
if (s.ok() && !empty) {
|
||||
meta->fd.file_size = builder->FileSize();
|
||||
uint64_t file_size = builder->FileSize();
|
||||
meta->fd.file_size = file_size;
|
||||
meta->marked_for_compaction = builder->NeedCompact();
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder->GetTableProperties();
|
||||
if (table_properties) {
|
||||
*table_properties = builder->GetTableProperties();
|
||||
*table_properties = tp;
|
||||
}
|
||||
}
|
||||
delete builder;
|
||||
@@ -177,6 +191,12 @@ Status BuildTable(
|
||||
if (!s.ok() || meta->fd.GetFileSize() == 0) {
|
||||
env->DeleteFile(fname);
|
||||
}
|
||||
|
||||
// Output to event logger and fire events.
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+10
-4
@@ -12,11 +12,13 @@
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/immutable_options.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "util/event_logger.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -36,6 +38,8 @@ class InternalIterator;
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown. It must outlive the
|
||||
// TableBuilder returned by this function.
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary, or nullptr.
|
||||
TableBuilder* NewTableBuilder(
|
||||
const ImmutableCFOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
@@ -44,6 +48,7 @@ TableBuilder* NewTableBuilder(
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts,
|
||||
const std::string* compression_dict = nullptr,
|
||||
const bool skip_filters = false);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
@@ -66,7 +71,8 @@ extern Status BuildTable(
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr, int level = -1);
|
||||
|
||||
|
||||
@@ -446,6 +446,12 @@ void rocksdb_backup_engine_create_new_backup(rocksdb_backup_engine_t* be,
|
||||
SaveError(errptr, be->rep->CreateNewBackup(db->rep));
|
||||
}
|
||||
|
||||
void rocksdb_backup_engine_purge_old_backups(rocksdb_backup_engine_t* be,
|
||||
uint32_t num_backups_to_keep,
|
||||
char** errptr) {
|
||||
SaveError(errptr, be->rep->PurgeOldBackups(num_backups_to_keep));
|
||||
}
|
||||
|
||||
rocksdb_restore_options_t* rocksdb_restore_options_create() {
|
||||
return new rocksdb_restore_options_t;
|
||||
}
|
||||
@@ -825,6 +831,31 @@ rocksdb_iterator_t* rocksdb_create_iterator_cf(
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_create_iterators(
|
||||
rocksdb_t *db,
|
||||
rocksdb_readoptions_t* opts,
|
||||
rocksdb_column_family_handle_t** column_families,
|
||||
rocksdb_iterator_t** iterators,
|
||||
size_t size,
|
||||
char** errptr) {
|
||||
std::vector<ColumnFamilyHandle*> column_families_vec;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
column_families_vec.push_back(column_families[i]->rep);
|
||||
}
|
||||
|
||||
std::vector<Iterator*> res;
|
||||
Status status = db->rep->NewIterators(opts->rep, column_families_vec, &res);
|
||||
assert(res.size() == size);
|
||||
if (SaveError(errptr, status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
iterators[i] = new rocksdb_iterator_t;
|
||||
iterators[i]->rep = res[i];
|
||||
}
|
||||
}
|
||||
|
||||
const rocksdb_snapshot_t* rocksdb_create_snapshot(
|
||||
rocksdb_t* db) {
|
||||
rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
|
||||
@@ -1393,6 +1424,11 @@ void rocksdb_options_set_compaction_filter_factory(
|
||||
std::shared_ptr<CompactionFilterFactory>(factory);
|
||||
}
|
||||
|
||||
void rocksdb_options_compaction_readahead_size(
|
||||
rocksdb_options_t* opt, size_t s) {
|
||||
opt->rep.compaction_readahead_size = s;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_comparator(
|
||||
rocksdb_options_t* opt,
|
||||
rocksdb_comparator_t* cmp) {
|
||||
@@ -1536,11 +1572,13 @@ void rocksdb_options_set_compression_per_level(rocksdb_options_t* opt,
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compression_options(
|
||||
rocksdb_options_t* opt, int w_bits, int level, int strategy) {
|
||||
void rocksdb_options_set_compression_options(rocksdb_options_t* opt, int w_bits,
|
||||
int level, int strategy,
|
||||
int max_dict_bytes) {
|
||||
opt->rep.compression_opts.window_bits = w_bits;
|
||||
opt->rep.compression_opts.level = level;
|
||||
opt->rep.compression_opts.strategy = strategy;
|
||||
opt->rep.compression_opts.max_dict_bytes = max_dict_bytes;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_prefix_extractor(
|
||||
@@ -2132,6 +2170,13 @@ rocksdb_env_t* rocksdb_create_default_env() {
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_env_t* rocksdb_create_mem_env() {
|
||||
rocksdb_env_t* result = new rocksdb_env_t;
|
||||
result->rep = rocksdb::NewMemEnv(Env::Default());
|
||||
result->is_default = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_env_set_background_threads(rocksdb_env_t* env, int n) {
|
||||
env->rep->SetBackgroundThreads(n);
|
||||
}
|
||||
|
||||
+43
-1
@@ -322,7 +322,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_block_based_table_factory(options, table_options);
|
||||
|
||||
rocksdb_options_set_compression(options, rocksdb_no_compression);
|
||||
rocksdb_options_set_compression_options(options, -14, -1, 0);
|
||||
rocksdb_options_set_compression_options(options, -14, -1, 0, 0);
|
||||
int compression_levels[] = {rocksdb_no_compression, rocksdb_no_compression,
|
||||
rocksdb_no_compression, rocksdb_no_compression};
|
||||
rocksdb_options_set_compression_per_level(options, compression_levels, 4);
|
||||
@@ -365,6 +365,24 @@ int main(int argc, char** argv) {
|
||||
rocksdb_backup_engine_create_new_backup(be, db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// need a change to trigger a new backup
|
||||
rocksdb_delete(db, woptions, "does-not-exist", 14, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_backup_engine_create_new_backup(be, db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
const rocksdb_backup_engine_info_t* bei = rocksdb_backup_engine_get_backup_info(be);
|
||||
CheckCondition(rocksdb_backup_engine_info_count(bei) > 1);
|
||||
rocksdb_backup_engine_info_destroy(bei);
|
||||
|
||||
rocksdb_backup_engine_purge_old_backups(be, 1, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
bei = rocksdb_backup_engine_get_backup_info(be);
|
||||
CheckCondition(rocksdb_backup_engine_info_count(bei) == 1);
|
||||
rocksdb_backup_engine_info_destroy(bei);
|
||||
|
||||
rocksdb_delete(db, woptions, "foo", 3, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
@@ -768,6 +786,30 @@ int main(int argc, char** argv) {
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
rocksdb_column_family_handle_t* iters_cf_handles[2] = { handles[0], handles[1] };
|
||||
rocksdb_iterator_t* iters_handles[2];
|
||||
rocksdb_create_iterators(db, roptions, iters_cf_handles, iters_handles, 2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
iter = iters_handles[0];
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
iter = iters_handles[1];
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
|
||||
for (i = 0; rocksdb_iter_valid(iter) != 0; rocksdb_iter_next(iter)) {
|
||||
i++;
|
||||
}
|
||||
CheckCondition(i == 1);
|
||||
rocksdb_iter_get_error(iter, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
rocksdb_drop_column_family(db, handles[1], &err);
|
||||
CheckNoError(err);
|
||||
for (i = 0; i < 2; i++) {
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
result.comparator = icmp;
|
||||
size_t clamp_max = std::conditional<
|
||||
sizeof(size_t) == 4, std::integral_constant<size_t, 0xffffffff>,
|
||||
std::integral_constant<size_t, 64ull << 30>>::type::value;
|
||||
std::integral_constant<uint64_t, 64ull << 30>>::type::value;
|
||||
ClipToRange(&result.write_buffer_size, ((size_t)64) << 10, clamp_max);
|
||||
// if user sets arena_block_size, we trust user to use this value. Otherwise,
|
||||
// calculate a proper value from writer_buffer_size;
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ class ColumnFamilyData {
|
||||
const ImmutableCFOptions* ioptions() const { return &ioptions_; }
|
||||
// REQUIRES: DB mutex held
|
||||
// This returns the MutableCFOptions used by current SuperVersion
|
||||
// You shoul use this API to reference MutableCFOptions most of the time.
|
||||
// You should use this API to reference MutableCFOptions most of the time.
|
||||
const MutableCFOptions* GetCurrentMutableCFOptions() const {
|
||||
return &(super_version_->mutable_cf_options);
|
||||
}
|
||||
|
||||
@@ -1042,6 +1042,7 @@ TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
@@ -1134,6 +1135,7 @@ TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
@@ -1229,6 +1231,7 @@ TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
@@ -1327,6 +1330,7 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
@@ -1416,6 +1420,7 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
@@ -1512,6 +1517,7 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticConflict) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
@@ -1631,6 +1637,7 @@ TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
@@ -2557,6 +2564,9 @@ TEST_F(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) {
|
||||
ASSERT_EQ(2, dbfull()->BGCompactionsAllowed());
|
||||
}
|
||||
|
||||
// Disable on windows because SyncWAL requires env->IsSyncThreadSafe()
|
||||
// to return true which is not so in unbuffered mode.
|
||||
#ifndef OS_WIN
|
||||
TEST_F(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
@@ -2586,6 +2596,7 @@ TEST_F(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
Close();
|
||||
}
|
||||
#endif
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -93,7 +93,7 @@ Status CompactedDBImpl::Init(const Options& options) {
|
||||
mutex_.Lock();
|
||||
ColumnFamilyDescriptor cf(kDefaultColumnFamilyName,
|
||||
ColumnFamilyOptions(options));
|
||||
Status s = Recover({ cf }, true /* read only */, false);
|
||||
Status s = Recover({cf}, true /* read only */, false, true);
|
||||
if (s.ok()) {
|
||||
cfd_ = reinterpret_cast<ColumnFamilyHandleImpl*>(
|
||||
DefaultColumnFamily())->cfd();
|
||||
|
||||
+1
-1
@@ -271,7 +271,7 @@ class Compaction {
|
||||
// A copy of inputs_, organized more closely in memory
|
||||
autovector<LevelFilesBrief, 2> input_levels_;
|
||||
|
||||
// State used to check for number of of overlapping grandparent files
|
||||
// State used to check for number of overlapping grandparent files
|
||||
// (grandparent == "output_level_ + 1")
|
||||
std::vector<FileMetaData*> grandparents_;
|
||||
const double score_; // score that was used to pick this compaction.
|
||||
|
||||
@@ -423,15 +423,15 @@ void CompactionIterator::PrepareOutput() {
|
||||
inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
SequenceNumber in, SequenceNumber* prev_snapshot) {
|
||||
assert(snapshots_->size());
|
||||
SequenceNumber prev __attribute__((unused)) = 0;
|
||||
SequenceNumber prev __attribute__((__unused__)) = kMaxSequenceNumber;
|
||||
for (const auto cur : *snapshots_) {
|
||||
assert(prev <= cur);
|
||||
assert(prev == kMaxSequenceNumber || prev <= cur);
|
||||
if (cur >= in) {
|
||||
*prev_snapshot = prev;
|
||||
*prev_snapshot = prev == kMaxSequenceNumber ? 0 : prev;
|
||||
return cur;
|
||||
}
|
||||
prev = cur;
|
||||
assert(prev);
|
||||
assert(prev < kMaxSequenceNumber);
|
||||
}
|
||||
*prev_snapshot = prev;
|
||||
return kMaxSequenceNumber;
|
||||
|
||||
+123
-26
@@ -16,12 +16,13 @@
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "db/db_iter.h"
|
||||
@@ -104,14 +105,14 @@ struct CompactionJob::SubcompactionState {
|
||||
uint64_t num_output_records;
|
||||
CompactionJobStats compaction_job_stats;
|
||||
uint64_t approx_size;
|
||||
// An index that used to speed up Compaction::ShouldStopBefore().
|
||||
// An index that used to speed up ShouldStopBefore().
|
||||
size_t grandparent_index = 0;
|
||||
// The number of bytes overlapping between the current output and
|
||||
// grandparent files used in Compaction::ShouldStopBefore().
|
||||
// grandparent files used in ShouldStopBefore().
|
||||
uint64_t overlapped_bytes = 0;
|
||||
// A flag determine whether the key has been seen in
|
||||
// Compaction::ShouldStopBefore()
|
||||
// A flag determine whether the key has been seen in ShouldStopBefore()
|
||||
bool seen_key = false;
|
||||
std::string compression_dict;
|
||||
|
||||
SubcompactionState(Compaction* c, Slice* _start, Slice* _end,
|
||||
uint64_t size = 0)
|
||||
@@ -126,7 +127,8 @@ struct CompactionJob::SubcompactionState {
|
||||
approx_size(size),
|
||||
grandparent_index(0),
|
||||
overlapped_bytes(0),
|
||||
seen_key(false) {
|
||||
seen_key(false),
|
||||
compression_dict() {
|
||||
assert(compaction != nullptr);
|
||||
}
|
||||
|
||||
@@ -148,6 +150,7 @@ struct CompactionJob::SubcompactionState {
|
||||
grandparent_index = std::move(o.grandparent_index);
|
||||
overlapped_bytes = std::move(o.overlapped_bytes);
|
||||
seen_key = std::move(o.seen_key);
|
||||
compression_dict = std::move(o.compression_dict);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -174,7 +177,7 @@ struct CompactionJob::SubcompactionState {
|
||||
assert(grandparent_index + 1 >= grandparents.size() ||
|
||||
icmp->Compare(
|
||||
grandparents[grandparent_index]->largest.Encode(),
|
||||
grandparents[grandparent_index + 1]->smallest.Encode()) < 0);
|
||||
grandparents[grandparent_index + 1]->smallest.Encode()) <= 0);
|
||||
grandparent_index++;
|
||||
}
|
||||
seen_key = true;
|
||||
@@ -666,6 +669,31 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
const MutableCFOptions* mutable_cf_options =
|
||||
sub_compact->compaction->mutable_cf_options();
|
||||
|
||||
// To build compression dictionary, we sample the first output file, assuming
|
||||
// it'll reach the maximum length, and then use the dictionary for compressing
|
||||
// subsequent output files. The dictionary may be less than max_dict_bytes if
|
||||
// the first output file's length is less than the maximum.
|
||||
const int kSampleLenShift = 6; // 2^6 = 64-byte samples
|
||||
std::set<size_t> sample_begin_offsets;
|
||||
if (bottommost_level_ &&
|
||||
cfd->ioptions()->compression_opts.max_dict_bytes > 0) {
|
||||
const size_t kMaxSamples =
|
||||
cfd->ioptions()->compression_opts.max_dict_bytes >> kSampleLenShift;
|
||||
const size_t kOutFileLen = mutable_cf_options->MaxFileSizeForLevel(
|
||||
compact_->compaction->output_level());
|
||||
if (kOutFileLen != port::kMaxSizet) {
|
||||
const size_t kOutFileNumSamples = kOutFileLen >> kSampleLenShift;
|
||||
Random64 generator{versions_->NewFileNumber()};
|
||||
for (size_t i = 0; i < kMaxSamples; ++i) {
|
||||
sample_begin_offsets.insert(generator.Uniform(kOutFileNumSamples)
|
||||
<< kSampleLenShift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto compaction_filter = cfd->ioptions()->compaction_filter;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr;
|
||||
if (compaction_filter == nullptr) {
|
||||
@@ -701,6 +729,13 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
const auto& c_iter_stats = c_iter->iter_stats();
|
||||
auto sample_begin_offset_iter = sample_begin_offsets.cbegin();
|
||||
// data_begin_offset and compression_dict are only valid while generating
|
||||
// dictionary from the first output file.
|
||||
size_t data_begin_offset = 0;
|
||||
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) &&
|
||||
@@ -744,6 +779,55 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
key, c_iter->ikey().sequence);
|
||||
sub_compact->num_output_records++;
|
||||
|
||||
if (sub_compact->outputs.size() == 1) { // first output file
|
||||
// Check if this key/value overlaps any sample intervals; if so, appends
|
||||
// overlapping portions to the dictionary.
|
||||
for (const auto& data_elmt : {key, value}) {
|
||||
size_t data_end_offset = data_begin_offset + data_elmt.size();
|
||||
while (sample_begin_offset_iter != sample_begin_offsets.cend() &&
|
||||
*sample_begin_offset_iter < data_end_offset) {
|
||||
size_t sample_end_offset =
|
||||
*sample_begin_offset_iter + (1 << kSampleLenShift);
|
||||
// Invariant: Because we advance sample iterator while processing the
|
||||
// data_elmt containing the sample's last byte, the current sample
|
||||
// cannot end before the current data_elmt.
|
||||
assert(data_begin_offset < sample_end_offset);
|
||||
|
||||
size_t data_elmt_copy_offset, data_elmt_copy_len;
|
||||
if (*sample_begin_offset_iter <= data_begin_offset) {
|
||||
// The sample starts before data_elmt starts, so take bytes starting
|
||||
// at the beginning of data_elmt.
|
||||
data_elmt_copy_offset = 0;
|
||||
} else {
|
||||
// data_elmt starts before the sample starts, so take bytes starting
|
||||
// at the below offset into data_elmt.
|
||||
data_elmt_copy_offset =
|
||||
*sample_begin_offset_iter - data_begin_offset;
|
||||
}
|
||||
if (sample_end_offset <= data_end_offset) {
|
||||
// The sample ends before data_elmt ends, so take as many bytes as
|
||||
// needed.
|
||||
data_elmt_copy_len =
|
||||
sample_end_offset - (data_begin_offset + data_elmt_copy_offset);
|
||||
} else {
|
||||
// data_elmt ends before the sample ends, so take all remaining
|
||||
// bytes in data_elmt.
|
||||
data_elmt_copy_len =
|
||||
data_end_offset - (data_begin_offset + data_elmt_copy_offset);
|
||||
}
|
||||
compression_dict.append(&data_elmt.data()[data_elmt_copy_offset],
|
||||
data_elmt_copy_len);
|
||||
if (sample_end_offset > data_end_offset) {
|
||||
// Didn't finish sample. Try to finish it with the next data_elmt.
|
||||
break;
|
||||
}
|
||||
// Next sample may require bytes from same data_elmt.
|
||||
sample_begin_offset_iter++;
|
||||
}
|
||||
data_begin_offset = data_end_offset;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -752,8 +836,12 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (sub_compact->builder->FileSize() >=
|
||||
sub_compact->compaction->max_output_file_size()) {
|
||||
status = FinishCompactionOutputFile(input->status(), sub_compact);
|
||||
if (sub_compact->outputs.size() == 1) {
|
||||
// Use dictionary from first output file for compression of subsequent
|
||||
// files.
|
||||
sub_compact->compression_dict = std::move(compression_dict);
|
||||
}
|
||||
}
|
||||
|
||||
c_iter->Next();
|
||||
}
|
||||
|
||||
@@ -866,9 +954,10 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
sub_compact->outfile.reset();
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
TableProperties tp;
|
||||
if (s.ok() && current_entries > 0) {
|
||||
// Verify that the table is usable
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), meta->fd,
|
||||
nullptr, cfd->internal_stats()->GetFileReadHist(
|
||||
@@ -882,34 +971,30 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
|
||||
delete iter;
|
||||
|
||||
// Output to event logger and fire events.
|
||||
if (s.ok()) {
|
||||
auto tp = sub_compact->builder->GetTableProperties();
|
||||
tp = sub_compact->builder->GetTableProperties();
|
||||
sub_compact->current_output()->table_properties =
|
||||
std::make_shared<TableProperties>(tp);
|
||||
TableFileCreationInfo info(std::move(tp));
|
||||
info.db_name = dbname_;
|
||||
info.cf_name = cfd->GetName();
|
||||
info.file_path =
|
||||
TableFileName(cfd->ioptions()->db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
info.file_size = meta->fd.GetFileSize();
|
||||
info.job_id = job_id_;
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64
|
||||
" keys, %" PRIu64 " bytes%s",
|
||||
cfd->GetName().c_str(), job_id_, output_number, current_entries,
|
||||
current_bytes,
|
||||
meta->marked_for_compaction ? " (need compaction)" : "");
|
||||
EventHelpers::LogAndNotifyTableFileCreation(
|
||||
event_logger_, cfd->ioptions()->listeners, meta->fd, info);
|
||||
}
|
||||
}
|
||||
std::string fname = TableFileName(db_options_.db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname,
|
||||
job_id_, meta->fd, tp, TableFileCreationReason::kCompaction, s);
|
||||
|
||||
// Report new file to SstFileManagerImpl
|
||||
auto sfm =
|
||||
static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
|
||||
if (sfm && meta->fd.GetPathId() == 0) {
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
auto fn = TableFileName(cfd->ioptions()->db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
sfm->OnAddFile(fn);
|
||||
@@ -985,10 +1070,17 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
assert(sub_compact->builder == nullptr);
|
||||
// no need to lock because VersionSet::next_file_number_ is atomic
|
||||
uint64_t file_number = versions_->NewFileNumber();
|
||||
// Make the output file
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
std::string fname = TableFileName(db_options_.db_paths, file_number,
|
||||
sub_compact->compaction->output_path_id());
|
||||
// Fire events.
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
#ifndef ROCKSDB_LITE
|
||||
EventHelpers::NotifyTableFileCreationStarted(
|
||||
cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname, job_id_,
|
||||
TableFileCreationReason::kCompaction);
|
||||
#endif // !ROCKSDB_LITE
|
||||
// Make the output file
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
Status s = NewWritableFile(env_, fname, &writable_file, env_options_);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::ERROR_LEVEL, db_options_.info_log,
|
||||
@@ -997,8 +1089,13 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_, file_number, s.ToString().c_str());
|
||||
LogFlush(db_options_.info_log);
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(),
|
||||
fname, job_id_, FileDescriptor(), TableProperties(),
|
||||
TableFileCreationReason::kCompaction, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
SubcompactionState::Output out;
|
||||
out.meta.fd =
|
||||
FileDescriptor(file_number, sub_compact->compaction->output_path_id(), 0);
|
||||
@@ -1011,7 +1108,6 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->outfile.reset(
|
||||
new WritableFileWriter(std::move(writable_file), env_options_));
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
// If the Column family flag is to only optimize filters for hits,
|
||||
// we can skip creating filters if this is the bottommost_level where
|
||||
// data is going to be found
|
||||
@@ -1021,7 +1117,8 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
*cfd->ioptions(), cfd->internal_comparator(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
sub_compact->outfile.get(), sub_compact->compaction->output_compression(),
|
||||
cfd->ioptions()->compression_opts, skip_filters));
|
||||
cfd->ioptions()->compression_opts, &sub_compact->compression_dict,
|
||||
skip_filters));
|
||||
LogFlush(db_options_.info_log);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -624,7 +624,10 @@ CompressionType GetAnyCompression() {
|
||||
return kBZip2Compression;
|
||||
} else if (LZ4_Supported()) {
|
||||
return kLZ4Compression;
|
||||
} else if (XPRESS_Supported()) {
|
||||
return kXpressCompression;
|
||||
}
|
||||
|
||||
return kNoCompression;
|
||||
}
|
||||
|
||||
@@ -660,7 +663,7 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.bytes_per_sync = 512 * 1024;
|
||||
|
||||
options.compaction_measure_io_stats = true;
|
||||
options.report_bg_io_stats = true;
|
||||
for (int test = 0; test < 2; ++test) {
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -79,7 +79,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
}
|
||||
|
||||
void Add(int level, uint32_t file_number, const char* smallest,
|
||||
const char* largest, uint64_t file_size = 0, uint32_t path_id = 0,
|
||||
const char* largest, uint64_t file_size = 1, uint32_t path_id = 0,
|
||||
SequenceNumber smallest_seq = 100,
|
||||
SequenceNumber largest_seq = 100) {
|
||||
assert(level < vstorage_->num_levels());
|
||||
@@ -320,6 +320,7 @@ TEST_F(CompactionPickerTest, Level0TriggerDynamic4) {
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 200;
|
||||
mutable_cf_options_.max_bytes_for_level_multiplier = 10;
|
||||
|
||||
NewVersionStorage(num_levels, kCompactionStyleLevel);
|
||||
Add(0, 1U, "150", "200");
|
||||
Add(0, 2U, "200", "250");
|
||||
@@ -352,6 +353,7 @@ TEST_F(CompactionPickerTest, LevelTriggerDynamic4) {
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 200;
|
||||
mutable_cf_options_.max_bytes_for_level_multiplier = 10;
|
||||
mutable_cf_options_.compaction_pri = kMinOverlappingRatio;
|
||||
NewVersionStorage(num_levels, kCompactionStyleLevel);
|
||||
Add(0, 1U, "150", "200");
|
||||
Add(num_levels - 1, 3U, "200", "250", 300U);
|
||||
@@ -367,11 +369,9 @@ TEST_F(CompactionPickerTest, LevelTriggerDynamic4) {
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(6U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(3U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(4U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(5U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(0, compaction->num_input_files(1));
|
||||
ASSERT_EQ(1U, compaction->num_input_levels());
|
||||
ASSERT_EQ(num_levels - 1, compaction->output_level());
|
||||
}
|
||||
|
||||
@@ -599,6 +599,8 @@ TEST_F(CompactionPickerTest, ParentIndexResetBug) {
|
||||
// ranges (with different sequence numbers) in the input files.
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.compaction_pri = kByCompensatedSize;
|
||||
|
||||
Add(1, 1U, "100", "150", 1U);
|
||||
// Overlapping user keys
|
||||
Add(1, 2U, "200", "400", 1U);
|
||||
|
||||
@@ -42,6 +42,7 @@ class CorruptionTest : public testing::Test {
|
||||
|
||||
CorruptionTest() {
|
||||
tiny_cache_ = NewLRUCache(100);
|
||||
options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options_.env = &env_;
|
||||
dbname_ = test::TmpDir() + "/corruption_test";
|
||||
DestroyDB(dbname_, options_);
|
||||
|
||||
+220
-6
@@ -12,11 +12,6 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static uint64_t TestGetTickerCount(const Options& options,
|
||||
Tickers ticker_type) {
|
||||
return options.statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
|
||||
class DBBlockCacheTest : public DBTestBase {
|
||||
private:
|
||||
size_t miss_count_ = 0;
|
||||
@@ -229,7 +224,226 @@ TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
}
|
||||
#endif
|
||||
#endif // SNAPPY
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
// Make sure that when options.block_cache is set, after a new table is
|
||||
// created its index/filter blocks are added to block cache.
|
||||
TEST_F(DBBlockCacheTest, IndexAndFilterBlocksOfNewTableAddedToCache) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
// Create a new table.
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// index/filter blocks added to block cache right after table creation.
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(2, /* only index/filter were added */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
|
||||
uint64_t int_num;
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
|
||||
// Make sure filter block is in cache.
|
||||
std::string value;
|
||||
ReadOptions ropt;
|
||||
db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
|
||||
|
||||
// Miss count should remain the same.
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
|
||||
db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
|
||||
// Make sure index block is in cache.
|
||||
auto index_block_hit = TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT);
|
||||
value = Get(1, "key");
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(index_block_hit + 1,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
|
||||
value = Get(1, "key");
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(index_block_hit + 2,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, ParanoidFileChecks) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.paranoid_file_checks = true;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "1_key", "val"));
|
||||
ASSERT_OK(Put(1, "9_key", "val"));
|
||||
// Create a new table.
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_EQ(1, /* read and cache data block */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
ASSERT_OK(Put(1, "1_key2", "val2"));
|
||||
ASSERT_OK(Put(1, "9_key2", "val2"));
|
||||
// Create a new SST file. This will further trigger a compaction
|
||||
// and generate another file.
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(3, /* Totally 3 files created up to now */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
// After disabling options.paranoid_file_checks. NO further block
|
||||
// is added after generating a new file.
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[1], {{"paranoid_file_checks", "false"}}));
|
||||
|
||||
ASSERT_OK(Put(1, "1_key3", "val3"));
|
||||
ASSERT_OK(Put(1, "9_key3", "val3"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "1_key4", "val4"));
|
||||
ASSERT_OK(Put(1, "9_key4", "val4"));
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(3, /* Totally 3 files created up to now */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, CompressedCache) {
|
||||
if (!Snappy_Supported()) {
|
||||
return;
|
||||
}
|
||||
int num_iter = 80;
|
||||
|
||||
// Run this test three iterations.
|
||||
// Iteration 1: only a uncompressed block cache
|
||||
// Iteration 2: only a compressed block cache
|
||||
// Iteration 3: both block cache and compressed cache
|
||||
// Iteration 4: both block cache and compressed cache, but DB is not
|
||||
// compressed
|
||||
for (int iter = 0; iter < 4; iter++) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 64 * 1024; // small write buffer
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
switch (iter) {
|
||||
case 0:
|
||||
// only uncompressed block cache
|
||||
table_options.block_cache = NewLRUCache(8 * 1024);
|
||||
table_options.block_cache_compressed = nullptr;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 1:
|
||||
// no block cache, only compressed cache
|
||||
table_options.no_block_cache = true;
|
||||
table_options.block_cache = nullptr;
|
||||
table_options.block_cache_compressed = NewLRUCache(8 * 1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 2:
|
||||
// both compressed and uncompressed block cache
|
||||
table_options.block_cache = NewLRUCache(1024);
|
||||
table_options.block_cache_compressed = NewLRUCache(8 * 1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 3:
|
||||
// both block cache and compressed cache, but DB is not compressed
|
||||
// also, make block cache sizes bigger, to trigger block cache hits
|
||||
table_options.block_cache = NewLRUCache(1024 * 1024);
|
||||
table_options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.compression = kNoCompression;
|
||||
break;
|
||||
default:
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
// default column family doesn't have block cache
|
||||
Options no_block_cache_opts;
|
||||
no_block_cache_opts.statistics = options.statistics;
|
||||
no_block_cache_opts = CurrentOptions(no_block_cache_opts);
|
||||
BlockBasedTableOptions table_options_no_bc;
|
||||
table_options_no_bc.no_block_cache = true;
|
||||
no_block_cache_opts.table_factory.reset(
|
||||
NewBlockBasedTableFactory(table_options_no_bc));
|
||||
ReopenWithColumnFamilies(
|
||||
{"default", "pikachu"},
|
||||
std::vector<Options>({no_block_cache_opts, options}));
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
// Write 8MB (80 values, each 100K)
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
std::vector<std::string> values;
|
||||
std::string str;
|
||||
for (int i = 0; i < num_iter; i++) {
|
||||
if (i % 4 == 0) { // high compression ratio
|
||||
str = RandomString(&rnd, 1000);
|
||||
}
|
||||
values.push_back(str);
|
||||
ASSERT_OK(Put(1, Key(i), values[i]));
|
||||
}
|
||||
|
||||
// flush all data from memtable so that reads are from block cache
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
for (int i = 0; i < num_iter; i++) {
|
||||
ASSERT_EQ(Get(1, Key(i)), values[i]);
|
||||
}
|
||||
|
||||
// check that we triggered the appropriate code paths in the cache
|
||||
switch (iter) {
|
||||
case 0:
|
||||
// only uncompressed block cache
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
case 1:
|
||||
// no block cache, only compressed cache
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
case 2:
|
||||
// both compressed and uncompressed block cache
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
case 3:
|
||||
// both compressed and uncompressed block cache
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_HIT), 0);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
// compressed doesn't have any hits since blocks are not compressed on
|
||||
// storage
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT), 0);
|
||||
break;
|
||||
default:
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+10
-36
@@ -40,34 +40,6 @@ class DBCompactionTestWithParam
|
||||
};
|
||||
|
||||
namespace {
|
||||
class OnFileDeletionListener : public EventListener {
|
||||
public:
|
||||
OnFileDeletionListener() :
|
||||
matched_count_(0),
|
||||
expected_file_name_("") {}
|
||||
|
||||
void SetExpectedFileName(
|
||||
const std::string file_name) {
|
||||
expected_file_name_ = file_name;
|
||||
}
|
||||
|
||||
void VerifyMatchedCount(size_t expected_value) {
|
||||
ASSERT_EQ(matched_count_, expected_value);
|
||||
}
|
||||
|
||||
void OnTableFileDeleted(
|
||||
const TableFileDeletionInfo& info) override {
|
||||
if (expected_file_name_ != "") {
|
||||
ASSERT_EQ(expected_file_name_, info.file_path);
|
||||
expected_file_name_ = "";
|
||||
matched_count_++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t matched_count_;
|
||||
std::string expected_file_name_;
|
||||
};
|
||||
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
@@ -98,8 +70,7 @@ class FlushedFileCollector : public EventListener {
|
||||
static const int kCDTValueSize = 1000;
|
||||
static const int kCDTKeysPerBuffer = 4;
|
||||
static const int kCDTNumLevels = 8;
|
||||
Options DeletionTriggerOptions() {
|
||||
Options options;
|
||||
Options DeletionTriggerOptions(Options options) {
|
||||
options.compression = kNoCompression;
|
||||
options.write_buffer_size = kCDTKeysPerBuffer * (kCDTValueSize + 24);
|
||||
options.min_write_buffer_number_to_merge = 1;
|
||||
@@ -202,7 +173,7 @@ const SstFileMetaData* PickFileRandomly(
|
||||
TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
for (int tid = 0; tid < 3; ++tid) {
|
||||
uint64_t db_size[2];
|
||||
Options options = CurrentOptions(DeletionTriggerOptions());
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
|
||||
if (tid == 1) {
|
||||
@@ -245,8 +216,7 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
// the compaction behavior when there are many of deletion entries.
|
||||
// The test will need to be updated if the internal behavior changes.
|
||||
|
||||
Options options = DeletionTriggerOptions();
|
||||
options = CurrentOptions(options);
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
@@ -377,7 +347,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) {
|
||||
for (int tid = 0; tid < 2; ++tid) {
|
||||
uint64_t db_size[3];
|
||||
Options options = CurrentOptions(DeletionTriggerOptions());
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
|
||||
if (tid == 1) {
|
||||
@@ -434,7 +404,7 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) {
|
||||
TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
uint64_t db_size[3];
|
||||
for (int test = 0; test < 2; ++test) {
|
||||
Options options = CurrentOptions(DeletionTriggerOptions());
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.skip_stats_update_on_db_open = (test == 0);
|
||||
|
||||
env_->random_read_counter_.Reset();
|
||||
@@ -986,7 +956,9 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
bool second = true;
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBCompaction::ManualPartial:4", "DBCompaction::ManualPartial:1"},
|
||||
{"DBCompaction::ManualPartial:2", "DBCompaction::ManualPartial:3"}});
|
||||
{"DBCompaction::ManualPartial:2", "DBCompaction::ManualPartial:3"},
|
||||
{"DBCompaction::ManualPartial:5",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) {
|
||||
if (first) {
|
||||
@@ -1097,6 +1069,8 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
|
||||
// 3 files in L0
|
||||
ASSERT_EQ("3,0,0,0,0,1,2", FilesPerLevel(0));
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:5");
|
||||
|
||||
// 1 file in L6, 1 file in L1
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
+111
-57
@@ -12,12 +12,14 @@
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#ifdef OS_SOLARIS
|
||||
#include <alloca.h>
|
||||
#endif
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
#include "jemalloc/jemalloc.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
@@ -201,6 +203,44 @@ Status SanitizeOptionsByTable(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
static Status ValidateOptions(
|
||||
const DBOptions& db_options,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families) {
|
||||
Status s;
|
||||
|
||||
for (auto& cfd : column_families) {
|
||||
s = CheckCompressionSupported(cfd.options);
|
||||
if (s.ok() && db_options.allow_concurrent_memtable_write) {
|
||||
s = CheckConcurrentWritesSupported(cfd.options);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (db_options.db_paths.size() > 1) {
|
||||
if ((cfd.options.compaction_style != kCompactionStyleUniversal) &&
|
||||
(cfd.options.compaction_style != kCompactionStyleLevel)) {
|
||||
return Status::NotSupported(
|
||||
"More than one DB paths are only supported in "
|
||||
"universal and level compaction styles. ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (db_options.db_paths.size() > 4) {
|
||||
return Status::NotSupported(
|
||||
"More than four DB paths are not supported yet. ");
|
||||
}
|
||||
|
||||
if (db_options.allow_mmap_reads && !db_options.allow_os_buffer) {
|
||||
// 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. ");
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
CompressionType GetCompressionFlush(const ImmutableCFOptions& ioptions) {
|
||||
// Compressing memtable flushes might not help unless the sequential load
|
||||
// optimization is used for leveled compaction. Otherwise the CPU and
|
||||
@@ -496,6 +536,39 @@ void DBImpl::PrintStatistics() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
typedef struct {
|
||||
char* cur;
|
||||
char* end;
|
||||
} MallocStatus;
|
||||
|
||||
static void GetJemallocStatus(void* mstat_arg, const char* status) {
|
||||
MallocStatus* mstat = reinterpret_cast<MallocStatus*>(mstat_arg);
|
||||
size_t status_len = status ? strlen(status) : 0;
|
||||
size_t buf_size = (size_t)(mstat->end - mstat->cur);
|
||||
if (!status_len || status_len > buf_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(mstat->cur, buf_size, "%s", status);
|
||||
mstat->cur += status_len;
|
||||
}
|
||||
#endif // ROCKSDB_JEMALLOC
|
||||
|
||||
static void DumpMallocStats(std::string* stats) {
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
MallocStatus mstat;
|
||||
const uint kMallocStatusLen = 1000000;
|
||||
std::unique_ptr<char> buf{new char[kMallocStatusLen + 1]};
|
||||
mstat.cur = buf.get();
|
||||
mstat.end = buf.get() + kMallocStatusLen;
|
||||
malloc_stats_print(GetJemallocStatus, &mstat, "");
|
||||
stats->append(buf.get());
|
||||
#endif // ROCKSDB_JEMALLOC
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void DBImpl::MaybeDumpStats() {
|
||||
if (db_options_.stats_dump_period_sec == 0) return;
|
||||
|
||||
@@ -528,6 +601,9 @@ void DBImpl::MaybeDumpStats() {
|
||||
default_cf_internal_stats_->GetStringProperty(
|
||||
*db_property_info, DB::Properties::kDBStats, &stats);
|
||||
}
|
||||
if (db_options_.dump_malloc_stats) {
|
||||
DumpMallocStats(&stats);
|
||||
}
|
||||
Log(InfoLogLevel::WARN_LEVEL,
|
||||
db_options_.info_log, "------- DUMPING STATS -------");
|
||||
Log(InfoLogLevel::WARN_LEVEL,
|
||||
@@ -950,7 +1026,7 @@ Directory* DBImpl::Directories::GetDataDir(size_t path_id) {
|
||||
|
||||
Status DBImpl::Recover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
bool error_if_log_file_exist) {
|
||||
bool error_if_log_file_exist, bool error_if_data_exists_in_logs) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
bool is_new_db = false;
|
||||
@@ -1045,10 +1121,25 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
|
||||
if (logs.size() > 0 && error_if_log_file_exist) {
|
||||
return Status::Corruption(""
|
||||
"The db was opened in readonly mode with error_if_log_file_exist"
|
||||
"flag but a log file already exists");
|
||||
if (logs.size() > 0) {
|
||||
if (error_if_log_file_exist) {
|
||||
return Status::Corruption(
|
||||
"The db was opened in readonly mode with error_if_log_file_exist"
|
||||
"flag but a log file already exists");
|
||||
} else if (error_if_data_exists_in_logs) {
|
||||
for (auto& log : logs) {
|
||||
std::string fname = LogFileName(db_options_.wal_dir, log);
|
||||
uint64_t bytes;
|
||||
s = env_->GetFileSize(fname, &bytes);
|
||||
if (s.ok()) {
|
||||
if (bytes > 0) {
|
||||
return Status::Corruption(
|
||||
"error_if_data_exists_in_logs is set but there are data "
|
||||
" in log files.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!logs.empty()) {
|
||||
@@ -1443,7 +1534,6 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
cfd->GetLatestMutableCFOptions()->paranoid_file_checks;
|
||||
{
|
||||
mutex_.Unlock();
|
||||
TableFileCreationInfo info;
|
||||
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
@@ -1456,26 +1546,14 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
GetCompressionFlush(*cfd->ioptions()),
|
||||
cfd->ioptions()->compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), Env::IO_HIGH, &info.table_properties);
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery,
|
||||
&event_logger_, job_id);
|
||||
LogFlush(db_options_.info_log);
|
||||
Log(InfoLogLevel::DEBUG_LEVEL, db_options_.info_log,
|
||||
"[%s] [WriteLevel0TableForRecovery]"
|
||||
" Level-0 table #%" PRIu64 ": %" PRIu64 " bytes %s",
|
||||
cfd->GetName().c_str(), meta.fd.GetNumber(), meta.fd.GetFileSize(),
|
||||
s.ToString().c_str());
|
||||
|
||||
// output to event logger
|
||||
if (s.ok()) {
|
||||
info.db_name = dbname_;
|
||||
info.cf_name = cfd->GetName();
|
||||
info.file_path = TableFileName(db_options_.db_paths,
|
||||
meta.fd.GetNumber(),
|
||||
meta.fd.GetPathId());
|
||||
info.file_size = meta.fd.GetFileSize();
|
||||
info.job_id = job_id;
|
||||
EventHelpers::LogAndNotifyTableFileCreation(
|
||||
&event_logger_, db_options_.listeners, meta.fd, info);
|
||||
}
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
@@ -1513,12 +1591,13 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, db_options_, mutable_cf_options, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, snapshot_seqs,
|
||||
earliest_write_conflict_snapshot, job_context, log_buffer,
|
||||
directories_.GetDbDir(), directories_.GetDataDir(0U),
|
||||
GetCompressionFlush(*cfd->ioptions()), stats_, &event_logger_);
|
||||
FlushJob flush_job(dbname_, cfd, db_options_, mutable_cf_options,
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
job_context, log_buffer, directories_.GetDbDir(),
|
||||
directories_.GetDataDir(0U),
|
||||
GetCompressionFlush(*cfd->ioptions()), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats);
|
||||
|
||||
FileMetaData file_meta;
|
||||
|
||||
@@ -1547,7 +1626,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// true, mark DB read-only
|
||||
bg_error_ = s;
|
||||
}
|
||||
RecordFlushIOStats();
|
||||
if (s.ok()) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// may temporarily unlock and lock the mutex.
|
||||
@@ -1867,7 +1945,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
directories_.GetDataDir(c->output_path_id()), stats_, &mutex_, &bg_error_,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, table_cache_,
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->compaction_measure_io_stats, dbname_,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
nullptr); // Here we pass a nullptr for CompactionJobStats because
|
||||
// CompactFiles does not trigger OnCompactionCompleted(),
|
||||
// which is the only place where CompactionJobStats is
|
||||
@@ -2616,11 +2694,6 @@ void DBImpl::SchedulePendingCompaction(ColumnFamilyData* cfd) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::RecordFlushIOStats() {
|
||||
RecordTick(stats_, FLUSH_WRITE_BYTES, IOSTATS(bytes_written));
|
||||
IOSTATS_RESET(bytes_written);
|
||||
}
|
||||
|
||||
void DBImpl::BGWorkFlush(void* db) {
|
||||
IOSTATS_SET_THREAD_POOL_ID(Env::Priority::HIGH);
|
||||
TEST_SYNC_POINT("DBImpl::BGWorkFlush");
|
||||
@@ -2754,7 +2827,6 @@ void DBImpl::BackgroundCallFlush() {
|
||||
bg_flush_scheduled_--;
|
||||
// See if there's more work to be done
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
RecordFlushIOStats();
|
||||
bg_cv_.SignalAll();
|
||||
// IMPORTANT: there should be no code after calling SignalAll. This call may
|
||||
// signal the DB destructor that it's OK to proceed with destruction. In
|
||||
@@ -3073,7 +3145,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
&bg_error_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->compaction_measure_io_stats, dbname_,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats);
|
||||
compaction_job.Prepare();
|
||||
|
||||
@@ -5388,27 +5460,9 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
return s;
|
||||
}
|
||||
|
||||
for (auto& cfd : column_families) {
|
||||
s = CheckCompressionSupported(cfd.options);
|
||||
if (s.ok() && db_options.allow_concurrent_memtable_write) {
|
||||
s = CheckConcurrentWritesSupported(cfd.options);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (db_options.db_paths.size() > 1) {
|
||||
if ((cfd.options.compaction_style != kCompactionStyleUniversal) &&
|
||||
(cfd.options.compaction_style != kCompactionStyleLevel)) {
|
||||
return Status::NotSupported(
|
||||
"More than one DB paths are only supported in "
|
||||
"universal and level compaction styles. ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (db_options.db_paths.size() > 4) {
|
||||
return Status::NotSupported(
|
||||
"More than four DB paths are not supported yet. ");
|
||||
s = ValidateOptions(db_options, column_families);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
*dbptr = nullptr;
|
||||
|
||||
+3
-5
@@ -232,7 +232,7 @@ class DBImpl : public DB {
|
||||
// SST files will also be checked.
|
||||
//
|
||||
// If a key is found, *found_record_for_key will be set to true and
|
||||
// *seq will will be set to the stored sequence number for the latest
|
||||
// *seq will be set to the stored sequence number for the latest
|
||||
// operation on this key or kMaxSequenceNumber if unknown.
|
||||
// If no key is found, *found_record_for_key will be set to false.
|
||||
//
|
||||
@@ -483,7 +483,8 @@ class DBImpl : public DB {
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
// be made to the descriptor are added to *edit.
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_log_file_exist = false);
|
||||
bool read_only = false, bool error_if_log_file_exist = false,
|
||||
bool error_if_data_exists_in_logs = false);
|
||||
|
||||
void MaybeIgnoreError(Status* s) const;
|
||||
|
||||
@@ -544,9 +545,6 @@ class DBImpl : public DB {
|
||||
// Wait for memtable flushed
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd);
|
||||
|
||||
void RecordFlushIOStats();
|
||||
void RecordCompactionIOStats();
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status CompactFilesImpl(
|
||||
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
|
||||
|
||||
+102
-74
@@ -16,6 +16,7 @@
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -103,7 +104,7 @@ class DBIter: public Iterator {
|
||||
InternalIterator* iter, SequenceNumber s, bool arena_mode,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
const Slice* iterate_upper_bound = nullptr,
|
||||
bool prefix_same_as_start = false)
|
||||
bool prefix_same_as_start = false, bool pin_data = false)
|
||||
: arena_mode_(arena_mode),
|
||||
env_(env),
|
||||
logger_(ioptions.info_log),
|
||||
@@ -118,12 +119,20 @@ class DBIter: public Iterator {
|
||||
version_number_(version_number),
|
||||
iterate_upper_bound_(iterate_upper_bound),
|
||||
prefix_same_as_start_(prefix_same_as_start),
|
||||
iter_pinned_(false) {
|
||||
pin_thru_lifetime_(pin_data) {
|
||||
RecordTick(statistics_, NO_ITERATORS);
|
||||
prefix_extractor_ = ioptions.prefix_extractor;
|
||||
max_skip_ = max_sequential_skip_in_iterations;
|
||||
if (pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
if (iter_) {
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
}
|
||||
virtual ~DBIter() {
|
||||
// Release pinned data if any
|
||||
pinned_iters_mgr_.ReleasePinnedIterators();
|
||||
RecordTick(statistics_, NO_ITERATORS, -1);
|
||||
local_stats_.BumpGlobalStatistics(statistics_);
|
||||
if (!arena_mode_) {
|
||||
@@ -135,9 +144,7 @@ class DBIter: public Iterator {
|
||||
virtual void SetIter(InternalIterator* iter) {
|
||||
assert(iter_ == nullptr);
|
||||
iter_ = iter;
|
||||
if (iter_ && iter_pinned_) {
|
||||
iter_->PinData();
|
||||
}
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
virtual bool Valid() const override { return valid_; }
|
||||
virtual Slice key() const override {
|
||||
@@ -146,8 +153,13 @@ class DBIter: public Iterator {
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
assert(valid_);
|
||||
return (direction_ == kForward && !current_entry_is_merged_) ?
|
||||
iter_->value() : saved_value_;
|
||||
if (current_entry_is_merged_) {
|
||||
return saved_value_;
|
||||
} else if (direction_ == kReverse) {
|
||||
return pinned_value_;
|
||||
} else {
|
||||
return iter_->value();
|
||||
}
|
||||
}
|
||||
virtual Status status() const override {
|
||||
if (status_.ok()) {
|
||||
@@ -156,28 +168,6 @@ class DBIter: public Iterator {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
virtual Status PinData() {
|
||||
Status s;
|
||||
if (iter_) {
|
||||
s = iter_->PinData();
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Even if iter_ is nullptr, we set iter_pinned_ to true so that when
|
||||
// iter_ is updated using SetIter, we Pin it.
|
||||
iter_pinned_ = true;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
virtual Status ReleasePinnedData() {
|
||||
Status s;
|
||||
if (iter_) {
|
||||
s = iter_->ReleasePinnedData();
|
||||
}
|
||||
if (s.ok()) {
|
||||
iter_pinned_ = false;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status GetProperty(std::string prop_name,
|
||||
std::string* prop) override {
|
||||
@@ -192,7 +182,7 @@ class DBIter: public Iterator {
|
||||
return Status::OK();
|
||||
} else if (prop_name == "rocksdb.iterator.is-key-pinned") {
|
||||
if (valid_) {
|
||||
*prop = (iter_pinned_ && saved_key_.IsKeyPinned()) ? "1" : "0";
|
||||
*prop = (pin_thru_lifetime_ && saved_key_.IsKeyPinned()) ? "1" : "0";
|
||||
} else {
|
||||
*prop = "Iterator is not valid.";
|
||||
}
|
||||
@@ -215,11 +205,26 @@ class DBIter: public Iterator {
|
||||
bool FindValueForCurrentKeyUsingSeek();
|
||||
void FindPrevUserKey();
|
||||
void FindNextUserKey();
|
||||
inline void FindNextUserEntry(bool skipping);
|
||||
void FindNextUserEntryInternal(bool skipping);
|
||||
inline void FindNextUserEntry(bool skipping, bool prefix_check);
|
||||
void FindNextUserEntryInternal(bool skipping, bool prefix_check);
|
||||
bool ParseKey(ParsedInternalKey* key);
|
||||
void MergeValuesNewToOld();
|
||||
|
||||
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
||||
// is called
|
||||
void TempPinData() {
|
||||
if (!pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
}
|
||||
|
||||
// Release blocks pinned by TempPinData()
|
||||
void ReleaseTempPinnedData() {
|
||||
if (!pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.ReleasePinnedIterators();
|
||||
}
|
||||
}
|
||||
|
||||
inline void ClearSavedValue() {
|
||||
if (saved_value_.capacity() > 1048576) {
|
||||
std::string empty;
|
||||
@@ -241,6 +246,7 @@ class DBIter: public Iterator {
|
||||
Status status_;
|
||||
IterKey saved_key_;
|
||||
std::string saved_value_;
|
||||
Slice pinned_value_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
@@ -248,12 +254,16 @@ class DBIter: public Iterator {
|
||||
uint64_t max_skip_;
|
||||
uint64_t version_number_;
|
||||
const Slice* iterate_upper_bound_;
|
||||
IterKey prefix_start_;
|
||||
bool prefix_same_as_start_;
|
||||
bool iter_pinned_;
|
||||
IterKey prefix_start_buf_;
|
||||
Slice prefix_start_key_;
|
||||
const bool prefix_same_as_start_;
|
||||
// Means that we will pin all data blocks we read as long the Iterator
|
||||
// is not deleted, will be true if ReadOptions::pin_data is true
|
||||
const bool pin_thru_lifetime_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
|
||||
// No copying allowed
|
||||
DBIter(const DBIter&);
|
||||
@@ -276,6 +286,8 @@ void DBIter::Next() {
|
||||
assert(valid_);
|
||||
|
||||
if (direction_ == kReverse) {
|
||||
// We only pin blocks when doing kReverse
|
||||
ReleaseTempPinnedData();
|
||||
FindNextUserKey();
|
||||
direction_ = kForward;
|
||||
if (!iter_->Valid()) {
|
||||
@@ -300,12 +312,7 @@ void DBIter::Next() {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
FindNextUserEntry(true /* skipping the current user key */);
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_ &&
|
||||
prefix_extractor_->Transform(saved_key_.GetKey())
|
||||
.compare(prefix_start_.GetKey()) != 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
FindNextUserEntry(true /* skipping the current user key */, prefix_same_as_start_);
|
||||
if (statistics_ != nullptr && valid_) {
|
||||
local_stats_.next_found_count_++;
|
||||
local_stats_.bytes_read_ += (key().size() + value().size());
|
||||
@@ -320,13 +327,18 @@ void DBIter::Next() {
|
||||
//
|
||||
// NOTE: In between, saved_key_ can point to a user key that has
|
||||
// a delete marker
|
||||
inline void DBIter::FindNextUserEntry(bool skipping) {
|
||||
//
|
||||
// The prefix_check parameter controls whether we check the iterated
|
||||
// keys against the prefix of the seeked key. Set to false when
|
||||
// performing a seek without a key (e.g. SeekToFirst). Set to
|
||||
// prefix_same_as_start_ for other iterations.
|
||||
inline void DBIter::FindNextUserEntry(bool skipping, bool prefix_check) {
|
||||
PERF_TIMER_GUARD(find_next_user_entry_time);
|
||||
FindNextUserEntryInternal(skipping);
|
||||
FindNextUserEntryInternal(skipping, prefix_check);
|
||||
}
|
||||
|
||||
// Actual implementation of DBIter::FindNextUserEntry()
|
||||
void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
// Loop until we hit an acceptable entry to yield
|
||||
assert(iter_->Valid());
|
||||
assert(direction_ == kForward);
|
||||
@@ -341,6 +353,11 @@ void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (prefix_extractor_ && prefix_check &&
|
||||
prefix_extractor_->Transform(ikey.user_key).compare(prefix_start_key_) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ikey.sequence <= sequence_) {
|
||||
if (skipping &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0) {
|
||||
@@ -352,21 +369,24 @@ void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
case kTypeSingleDeletion:
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeValue:
|
||||
valid_ = true;
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
return;
|
||||
case kTypeMerge:
|
||||
// By now, we are sure the current ikey is going to yield a value
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
current_entry_is_merged_ = true;
|
||||
valid_ = true;
|
||||
MergeValuesNewToOld(); // Go to a different state machine
|
||||
@@ -477,6 +497,7 @@ void DBIter::Prev() {
|
||||
if (direction_ == kForward) {
|
||||
ReverseToBackward();
|
||||
}
|
||||
ReleaseTempPinnedData();
|
||||
PrevInternal();
|
||||
if (statistics_ != nullptr) {
|
||||
local_stats_.prev_count_++;
|
||||
@@ -487,7 +508,7 @@ void DBIter::Prev() {
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_ &&
|
||||
prefix_extractor_->Transform(saved_key_.GetKey())
|
||||
.compare(prefix_start_.GetKey()) != 0) {
|
||||
.compare(prefix_start_key_) != 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
@@ -529,7 +550,7 @@ void DBIter::PrevInternal() {
|
||||
|
||||
while (iter_->Valid()) {
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
if (FindValueForCurrentKey()) {
|
||||
valid_ = true;
|
||||
if (!iter_->Valid()) {
|
||||
@@ -560,6 +581,7 @@ void DBIter::PrevInternal() {
|
||||
bool DBIter::FindValueForCurrentKey() {
|
||||
assert(iter_->Valid());
|
||||
merge_context_.Clear();
|
||||
current_entry_is_merged_ = false;
|
||||
// last entry before merge (could be kTypeDeletion, kTypeSingleDeletion or
|
||||
// kTypeValue)
|
||||
ValueType last_not_merge_type = kTypeDeletion;
|
||||
@@ -580,7 +602,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeValue:
|
||||
merge_context_.Clear();
|
||||
saved_value_ = iter_->value().ToString();
|
||||
ReleaseTempPinnedData();
|
||||
TempPinData();
|
||||
pinned_value_ = iter_->value();
|
||||
last_not_merge_type = kTypeValue;
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
@@ -610,6 +634,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
valid_ = false;
|
||||
return false;
|
||||
case kTypeMerge:
|
||||
current_entry_is_merged_ = true;
|
||||
if (last_not_merge_type == kTypeDeletion) {
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
@@ -620,12 +645,10 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
timer.ElapsedNanos());
|
||||
} else {
|
||||
assert(last_not_merge_type == kTypeValue);
|
||||
std::string last_put_value = saved_value_;
|
||||
Slice temp_slice(last_put_value);
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &temp_slice,
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &pinned_value_,
|
||||
merge_context_.GetOperands(),
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
@@ -660,7 +683,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
if (ikey.type == kTypeValue || ikey.type == kTypeDeletion ||
|
||||
ikey.type == kTypeSingleDeletion) {
|
||||
if (ikey.type == kTypeValue) {
|
||||
saved_value_ = iter_->value().ToString();
|
||||
ReleaseTempPinnedData();
|
||||
TempPinData();
|
||||
pinned_value_ = iter_->value();
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
@@ -670,6 +695,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
|
||||
// kTypeMerge. We need to collect all kTypeMerge values and save them
|
||||
// in operands
|
||||
current_entry_is_merged_ = true;
|
||||
merge_context_.Clear();
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) &&
|
||||
@@ -772,6 +798,7 @@ void DBIter::FindParseableKey(ParsedInternalKey* ikey, Direction direction) {
|
||||
|
||||
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_);
|
||||
@@ -783,9 +810,15 @@ void DBIter::Seek(const Slice& target) {
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_key_ = prefix_extractor_->Transform(target);
|
||||
}
|
||||
direction_ = kForward;
|
||||
ClearSavedValue();
|
||||
FindNextUserEntry(false /* not skipping */);
|
||||
FindNextUserEntry(false /* not skipping */, prefix_same_as_start_);
|
||||
if (!valid_) {
|
||||
prefix_start_key_.clear();
|
||||
}
|
||||
if (statistics_ != nullptr) {
|
||||
if (valid_) {
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
@@ -796,7 +829,8 @@ void DBIter::Seek(const Slice& target) {
|
||||
valid_ = false;
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_.SetKey(prefix_extractor_->Transform(target));
|
||||
prefix_start_buf_.SetKey(prefix_start_key_);
|
||||
prefix_start_key_ = prefix_start_buf_.GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,6 +841,7 @@ void DBIter::SeekToFirst() {
|
||||
max_skip_ = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
direction_ = kForward;
|
||||
ReleaseTempPinnedData();
|
||||
ClearSavedValue();
|
||||
|
||||
{
|
||||
@@ -816,7 +851,7 @@ void DBIter::SeekToFirst() {
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
FindNextUserEntry(false /* not skipping */);
|
||||
FindNextUserEntry(false /* not skipping */, false /* no prefix check */);
|
||||
if (statistics_ != nullptr) {
|
||||
if (valid_) {
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
@@ -827,7 +862,8 @@ void DBIter::SeekToFirst() {
|
||||
valid_ = false;
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_.SetKey(prefix_extractor_->Transform(saved_key_.GetKey()));
|
||||
prefix_start_buf_.SetKey(prefix_extractor_->Transform(saved_key_.GetKey()));
|
||||
prefix_start_key_ = prefix_start_buf_.GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,6 +874,7 @@ void DBIter::SeekToLast() {
|
||||
max_skip_ = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
direction_ = kReverse;
|
||||
ReleaseTempPinnedData();
|
||||
ClearSavedValue();
|
||||
|
||||
{
|
||||
@@ -875,7 +912,8 @@ void DBIter::SeekToLast() {
|
||||
}
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_.SetKey(prefix_extractor_->Transform(saved_key_.GetKey()));
|
||||
prefix_start_buf_.SetKey(prefix_extractor_->Transform(saved_key_.GetKey()));
|
||||
prefix_start_key_ = prefix_start_buf_.GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -890,10 +928,7 @@ Iterator* NewDBIterator(Env* env, const ImmutableCFOptions& ioptions,
|
||||
DBIter* db_iter =
|
||||
new DBIter(env, ioptions, user_key_comparator, internal_iter, sequence,
|
||||
false, max_sequential_skip_in_iterations, version_number,
|
||||
iterate_upper_bound, prefix_same_as_start);
|
||||
if (pin_data) {
|
||||
db_iter->PinData();
|
||||
}
|
||||
iterate_upper_bound, prefix_same_as_start, pin_data);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
@@ -916,14 +951,10 @@ inline void ArenaWrappedDBIter::Prev() { db_iter_->Prev(); }
|
||||
inline Slice ArenaWrappedDBIter::key() const { return db_iter_->key(); }
|
||||
inline Slice ArenaWrappedDBIter::value() const { return db_iter_->value(); }
|
||||
inline Status ArenaWrappedDBIter::status() const { return db_iter_->status(); }
|
||||
inline Status ArenaWrappedDBIter::PinData() { return db_iter_->PinData(); }
|
||||
inline Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
|
||||
std::string* prop) {
|
||||
return db_iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
inline Status ArenaWrappedDBIter::ReleasePinnedData() {
|
||||
return db_iter_->ReleasePinnedData();
|
||||
}
|
||||
void ArenaWrappedDBIter::RegisterCleanup(CleanupFunction function, void* arg1,
|
||||
void* arg2) {
|
||||
db_iter_->RegisterCleanup(function, arg1, arg2);
|
||||
@@ -941,12 +972,9 @@ ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
DBIter* db_iter =
|
||||
new (mem) DBIter(env, ioptions, user_key_comparator, nullptr, sequence,
|
||||
true, max_sequential_skip_in_iterations, version_number,
|
||||
iterate_upper_bound, prefix_same_as_start);
|
||||
iterate_upper_bound, prefix_same_as_start, pin_data);
|
||||
|
||||
iter->SetDBIter(db_iter);
|
||||
if (pin_data) {
|
||||
iter->PinData();
|
||||
}
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
@@ -63,8 +63,6 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
virtual Status status() const override;
|
||||
|
||||
void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
|
||||
virtual Status PinData();
|
||||
virtual Status ReleasePinnedData();
|
||||
virtual Status GetProperty(std::string prop_name, std::string* prop) override;
|
||||
|
||||
private:
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -19,6 +19,7 @@
|
||||
#include "rocksdb/perf_level.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -223,7 +224,7 @@ void GetExpectedTableProperties(TableProperties* expected_tp,
|
||||
const int kBloomBitsPerKey,
|
||||
const size_t kBlockSize) {
|
||||
const int kKeyCount = kTableCount * kKeysPerTable;
|
||||
const int kAvgSuccessorSize = kKeySize / 2;
|
||||
const int kAvgSuccessorSize = kKeySize / 5;
|
||||
const int kEncodingSavePerKey = kKeySize / 4;
|
||||
expected_tp->raw_key_size = kKeyCount * (kKeySize + 8);
|
||||
expected_tp->raw_value_size = kKeyCount * kValueSize;
|
||||
@@ -898,6 +899,48 @@ TEST_F(DBPropertiesTest, EstimatePendingCompBytes) {
|
||||
"rocksdb.estimate-pending-compaction-bytes", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, EstimateCompressionRatio) {
|
||||
if (!Snappy_Supported()) {
|
||||
return;
|
||||
}
|
||||
const int kNumL0Files = 3;
|
||||
const int kNumEntriesPerFile = 1000;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compression_per_level = {kNoCompression, kSnappyCompression};
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_background_flushes = 0;
|
||||
options.num_levels = 2;
|
||||
Reopen(options);
|
||||
|
||||
// compression ratio is -1.0 when no open files at level
|
||||
ASSERT_EQ(CompressionRatioAtLevel(0), -1.0);
|
||||
|
||||
const std::string kVal(100, 'a');
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
// Put common data ("key") at end to prevent delta encoding from
|
||||
// compressing the key effectively
|
||||
std::string key = ToString(i) + ToString(j) + "key";
|
||||
ASSERT_OK(dbfull()->Put(WriteOptions(), key, kVal));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
// no compression at L0, so ratio is less than one
|
||||
ASSERT_LT(CompressionRatioAtLevel(0), 1.0);
|
||||
ASSERT_GT(CompressionRatioAtLevel(0), 0.0);
|
||||
ASSERT_EQ(CompressionRatioAtLevel(1), -1.0);
|
||||
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
||||
|
||||
ASSERT_EQ(CompressionRatioAtLevel(0), -1.0);
|
||||
// Data at L1 should be highly compressed thanks to Snappy and redundant data
|
||||
// in values (ratio is 12.846 as of 4/19/2016).
|
||||
ASSERT_GT(CompressionRatioAtLevel(1), 10.0);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
class CountingUserTblPropCollector : public TablePropertiesCollector {
|
||||
|
||||
+1299
File diff suppressed because it is too large.
Load diff
+238
-4737
File diff suppressed because it is too large.
Load diff
+160
-23
@@ -13,16 +13,73 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static uint64_t TestGetTickerCount(const Options& options,
|
||||
Tickers ticker_type) {
|
||||
return options.statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
|
||||
class DBTest2 : public DBTestBase {
|
||||
public:
|
||||
DBTest2() : DBTestBase("/db_test2") {}
|
||||
};
|
||||
|
||||
class PrefixFullBloomWithReverseComparator
|
||||
: public DBTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
PrefixFullBloomWithReverseComparator()
|
||||
: DBTestBase("/prefix_bloom_reverse") {}
|
||||
virtual void SetUp() override { if_cache_filter_ = GetParam(); }
|
||||
bool if_cache_filter_;
|
||||
};
|
||||
|
||||
TEST_P(PrefixFullBloomWithReverseComparator,
|
||||
PrefixFullBloomWithReverseComparator) {
|
||||
Options options = last_options_;
|
||||
options.comparator = ReverseBytewiseComparator();
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(3));
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
if (if_cache_filter_) {
|
||||
bbto.no_block_cache = false;
|
||||
bbto.cache_index_and_filter_blocks = true;
|
||||
bbto.block_cache = NewLRUCache(1);
|
||||
}
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
bbto.whole_key_filtering = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(dbfull()->Put(WriteOptions(), "bar123", "foo"));
|
||||
ASSERT_OK(dbfull()->Put(WriteOptions(), "bar234", "foo2"));
|
||||
ASSERT_OK(dbfull()->Put(WriteOptions(), "foo123", "foo3"));
|
||||
|
||||
dbfull()->Flush(FlushOptions());
|
||||
|
||||
if (bbto.block_cache) {
|
||||
bbto.block_cache->EraseUnRefEntries();
|
||||
}
|
||||
|
||||
unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
iter->Seek("bar345");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar234", iter->key().ToString());
|
||||
ASSERT_EQ("foo2", iter->value().ToString());
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar123", iter->key().ToString());
|
||||
ASSERT_EQ("foo", iter->value().ToString());
|
||||
|
||||
iter->Seek("foo234");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo123", iter->key().ToString());
|
||||
ASSERT_EQ("foo3", iter->value().ToString());
|
||||
|
||||
iter->Seek("bar");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PrefixFullBloomWithReverseComparator,
|
||||
PrefixFullBloomWithReverseComparator, testing::Bool());
|
||||
|
||||
TEST_F(DBTest2, IteratorPropertyVersionNumber) {
|
||||
Put("", "");
|
||||
Iterator* iter1 = db_->NewIterator(ReadOptions());
|
||||
@@ -390,8 +447,7 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatch) {
|
||||
|
||||
for (size_t i = 0; i < batch_keys.size(); i++) {
|
||||
for (size_t j = 0; j < batch_keys[i].size(); j++) {
|
||||
if (i >= change_records_from_index && j >=
|
||||
num_keys_to_add_in_new_batch) {
|
||||
if (i >= change_records_from_index && j >= num_keys_to_add_in_new_batch) {
|
||||
keys_must_not_exist.push_back(Slice(batch_keys[i][j]));
|
||||
}
|
||||
else {
|
||||
@@ -466,8 +522,7 @@ TEST_F(DBTest2, WalFilterTestWithChangeBatchExtraKeys) {
|
||||
// Reopen database with option to use WAL filter
|
||||
options = OptionsForLogIterTest();
|
||||
options.wal_filter = &test_wal_filter_extra_keys;
|
||||
Status status = TryReopenWithColumnFamilies({ "default", "pikachu" },
|
||||
options);
|
||||
Status status = TryReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_TRUE(status.IsNotSupported());
|
||||
|
||||
// Reopen without filter, now reopen should succeed - previous
|
||||
@@ -550,8 +605,7 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
|
||||
return "WalFilterTestWithColumnFamilies";
|
||||
}
|
||||
|
||||
const std::map<uint32_t, std::vector<std::string>> &
|
||||
GetColumnFamilyKeys() {
|
||||
const std::map<uint32_t, std::vector<std::string>>& GetColumnFamilyKeys() {
|
||||
return cf_wal_keys_;
|
||||
}
|
||||
|
||||
@@ -622,8 +676,7 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
|
||||
// verify that handles_[0] only has post_flush keys
|
||||
// while handles_[1] has pre and post flush keys
|
||||
auto cf_wal_keys = test_wal_filter_column_families.GetColumnFamilyKeys();
|
||||
auto name_id_map =
|
||||
test_wal_filter_column_families.GetColumnFamilyNameIdMap();
|
||||
auto name_id_map = test_wal_filter_column_families.GetColumnFamilyNameIdMap();
|
||||
size_t index = 0;
|
||||
auto keys_cf = cf_wal_keys[name_id_map[kDefaultColumnFamilyName]];
|
||||
//default column-family, only post_flush keys are expected
|
||||
@@ -656,23 +709,106 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
|
||||
}
|
||||
ASSERT_TRUE(index == keys_cf.size());
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, PresetCompressionDict) {
|
||||
const size_t kBlockSizeBytes = 4 << 10;
|
||||
const size_t kL0FileBytes = 128 << 10;
|
||||
const size_t kApproxPerBlockOverheadBytes = 50;
|
||||
const int kNumL0Files = 5;
|
||||
|
||||
Options options;
|
||||
options.arena_block_size = kBlockSizeBytes;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(kL0FileBytes / kBlockSizeBytes));
|
||||
options.num_levels = 2;
|
||||
options.target_file_size_base = kL0FileBytes;
|
||||
options.target_file_size_multiplier = 2;
|
||||
options.write_buffer_size = kL0FileBytes;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = kBlockSizeBytes;
|
||||
std::vector<CompressionType> compression_types;
|
||||
if (Zlib_Supported()) {
|
||||
compression_types.push_back(kZlibCompression);
|
||||
}
|
||||
#if LZ4_VERSION_NUMBER >= 10400 // r124+
|
||||
compression_types.push_back(kLZ4Compression);
|
||||
compression_types.push_back(kLZ4HCCompression);
|
||||
#endif // LZ4_VERSION_NUMBER >= 10400
|
||||
#if ZSTD_VERSION_NUMBER >= 500 // v0.5.0+
|
||||
compression_types.push_back(kZSTDNotFinalCompression);
|
||||
#endif // ZSTD_VERSION_NUMBER >= 500
|
||||
|
||||
for (auto compression_type : compression_types) {
|
||||
options.compression = compression_type;
|
||||
size_t prev_out_bytes;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// First iteration: compress without preset dictionary
|
||||
// Second iteration: compress with preset dictionary
|
||||
// To make sure the compression dictionary was actually used, we verify
|
||||
// the compressed size is smaller in the second iteration. Also in the
|
||||
// second iteration, verify the data we get out is the same data we put
|
||||
// in.
|
||||
if (i) {
|
||||
options.compression_opts.max_dict_bytes = kBlockSizeBytes;
|
||||
} else {
|
||||
options.compression_opts.max_dict_bytes = 0;
|
||||
}
|
||||
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Random rnd(301);
|
||||
std::string seq_data =
|
||||
RandomString(&rnd, kBlockSizeBytes - kApproxPerBlockOverheadBytes);
|
||||
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0, 1));
|
||||
for (int j = 0; j < kNumL0Files; ++j) {
|
||||
for (size_t k = 0; k < kL0FileBytes / kBlockSizeBytes + 1; ++k) {
|
||||
ASSERT_OK(Put(1, Key(static_cast<int>(
|
||||
j * (kL0FileBytes / kBlockSizeBytes) + k)),
|
||||
seq_data));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
ASSERT_EQ(j + 1, NumTableFilesAtLevel(0, 1));
|
||||
}
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0, 1));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1, 1), 0);
|
||||
|
||||
size_t out_bytes = 0;
|
||||
std::vector<std::string> files;
|
||||
GetSstFiles(dbname_, &files);
|
||||
for (const auto& file : files) {
|
||||
uint64_t curr_bytes;
|
||||
env_->GetFileSize(dbname_ + "/" + file, &curr_bytes);
|
||||
out_bytes += static_cast<size_t>(curr_bytes);
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < kNumL0Files * (kL0FileBytes / kBlockSizeBytes);
|
||||
j++) {
|
||||
ASSERT_EQ(seq_data, Get(1, Key(static_cast<int>(j))));
|
||||
}
|
||||
if (i) {
|
||||
ASSERT_GT(prev_out_bytes, out_bytes);
|
||||
}
|
||||
prev_out_bytes = out_bytes;
|
||||
DestroyAndReopen(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, DISABLED_FirstSnapshotTest) {
|
||||
TEST_F(DBTest2, FirstSnapshotTest) {
|
||||
Options options;
|
||||
options.write_buffer_size = 100000; // Small write buffer
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// This snapshot will have sequence number 0. When compaction encounters
|
||||
// this snapshot, CompactionIterator::findEarliestVisibleSnapshot() will
|
||||
// assert as it expects non-zero snapshots.
|
||||
//
|
||||
// One fix would be to simply remove this assert. However, a better fix
|
||||
// might
|
||||
// be to always have db sequence numbers start from 1 so that no code is
|
||||
// ever
|
||||
// confused by 0.
|
||||
// This snapshot will have sequence number 0 what is expected behaviour.
|
||||
const Snapshot* s1 = db_->GetSnapshot();
|
||||
|
||||
Put(1, "k1", std::string(100000, 'x')); // Fill memtable
|
||||
@@ -793,6 +929,7 @@ TEST_P(PinL0IndexAndFilterBlocksTest,
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PinL0IndexAndFilterBlocksTest,
|
||||
PinL0IndexAndFilterBlocksTest, ::testing::Bool());
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -212,6 +212,11 @@ Options DBTestBase::CurrentOptions(
|
||||
options.write_buffer_size = 4090 * 4096;
|
||||
options.target_file_size_base = 2 * 1024 * 1024;
|
||||
options.max_bytes_for_level_base = 10 * 1024 * 1024;
|
||||
options.max_open_files = 5000;
|
||||
options.base_background_compactions = -1;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options.compaction_pri = CompactionPri::kByCompensatedSize;
|
||||
|
||||
return CurrentOptions(options, options_override);
|
||||
}
|
||||
|
||||
@@ -303,6 +308,7 @@ Options DBTestBase::CurrentOptions(
|
||||
case kPerfOptions:
|
||||
options.soft_rate_limit = 2.0;
|
||||
options.delayed_write_rate = 8 * 1024 * 1024;
|
||||
options.report_bg_io_stats = true;
|
||||
// TODO(3.13) -- test more options
|
||||
break;
|
||||
case kDeletesFilterFirst:
|
||||
@@ -713,6 +719,22 @@ int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
|
||||
double DBTestBase::CompressionRatioAtLevel(int level, int cf) {
|
||||
std::string property;
|
||||
if (cf == 0) {
|
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.compression-ratio-at-level" + NumberToString(level),
|
||||
&property));
|
||||
} else {
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
handles_[cf],
|
||||
"rocksdb.compression-ratio-at-level" + NumberToString(level),
|
||||
&property));
|
||||
}
|
||||
return std::stod(property);
|
||||
}
|
||||
|
||||
int DBTestBase::TotalTableFiles(int cf, int levels) {
|
||||
if (levels == -1) {
|
||||
levels = CurrentOptions().num_levels;
|
||||
@@ -1042,4 +1064,34 @@ std::unordered_map<std::string, uint64_t> DBTestBase::GetAllSSTFiles(
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t> DBTestBase::ListTableFiles(Env* env,
|
||||
const std::string& path) {
|
||||
std::vector<std::string> files;
|
||||
std::vector<uint64_t> file_numbers;
|
||||
env->GetChildren(path, &files);
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
for (size_t i = 0; i < files.size(); ++i) {
|
||||
if (ParseFileName(files[i], &number, &type)) {
|
||||
if (type == kTableFile) {
|
||||
file_numbers.push_back(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
return file_numbers;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
uint64_t DBTestBase::GetNumberOfSstFilesForColumnFamily(
|
||||
DB* db, std::string column_family_name) {
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db->GetLiveFilesMetaData(&metadata);
|
||||
uint64_t result = 0;
|
||||
for (auto& fileMetadata : metadata) {
|
||||
result += (fileMetadata.column_family_name == column_family_name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
+53
-4
@@ -39,6 +39,7 @@
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
@@ -357,23 +358,30 @@ class SpecialEnv : public EnvWrapper {
|
||||
class CountingFile : public RandomAccessFile {
|
||||
public:
|
||||
CountingFile(unique_ptr<RandomAccessFile>&& target,
|
||||
anon::AtomicCounter* counter)
|
||||
: target_(std::move(target)), counter_(counter) {}
|
||||
anon::AtomicCounter* counter,
|
||||
std::atomic<size_t>* bytes_read)
|
||||
: target_(std::move(target)),
|
||||
counter_(counter),
|
||||
bytes_read_(bytes_read) {}
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
counter_->Increment();
|
||||
return target_->Read(offset, n, result, scratch);
|
||||
Status s = target_->Read(offset, n, result, scratch);
|
||||
*bytes_read_ += result->size();
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
unique_ptr<RandomAccessFile> target_;
|
||||
anon::AtomicCounter* counter_;
|
||||
std::atomic<size_t>* bytes_read_;
|
||||
};
|
||||
|
||||
Status s = target()->NewRandomAccessFile(f, r, soptions);
|
||||
random_file_open_counter_++;
|
||||
if (s.ok() && count_random_reads_) {
|
||||
r->reset(new CountingFile(std::move(*r), &random_read_counter_));
|
||||
r->reset(new CountingFile(std::move(*r), &random_read_counter_,
|
||||
&random_read_bytes_counter_));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -463,6 +471,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
|
||||
bool count_random_reads_;
|
||||
anon::AtomicCounter random_read_counter_;
|
||||
std::atomic<size_t> random_read_bytes_counter_;
|
||||
std::atomic<int> random_file_open_counter_;
|
||||
|
||||
bool count_sequential_reads_;
|
||||
@@ -491,6 +500,33 @@ class SpecialEnv : public EnvWrapper {
|
||||
std::atomic<bool> is_wal_sync_thread_safe_{true};
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class OnFileDeletionListener : public EventListener {
|
||||
public:
|
||||
OnFileDeletionListener() : matched_count_(0), expected_file_name_("") {}
|
||||
|
||||
void SetExpectedFileName(const std::string file_name) {
|
||||
expected_file_name_ = file_name;
|
||||
}
|
||||
|
||||
void VerifyMatchedCount(size_t expected_value) {
|
||||
ASSERT_EQ(matched_count_, expected_value);
|
||||
}
|
||||
|
||||
void OnTableFileDeleted(const TableFileDeletionInfo& info) override {
|
||||
if (expected_file_name_ != "") {
|
||||
ASSERT_EQ(expected_file_name_, info.file_path);
|
||||
expected_file_name_ = "";
|
||||
matched_count_++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t matched_count_;
|
||||
std::string expected_file_name_;
|
||||
};
|
||||
#endif
|
||||
|
||||
class DBTestBase : public testing::Test {
|
||||
protected:
|
||||
// Sequence of option configurations to try
|
||||
@@ -672,6 +708,8 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
int NumTableFilesAtLevel(int level, int cf = 0);
|
||||
|
||||
double CompressionRatioAtLevel(int level, int cf = 0);
|
||||
|
||||
int TotalTableFiles(int cf = 0, int levels = -1);
|
||||
|
||||
// Return spread of files per level
|
||||
@@ -756,6 +794,17 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
std::unordered_map<std::string, uint64_t> GetAllSSTFiles(
|
||||
uint64_t* total_size = nullptr);
|
||||
|
||||
std::vector<std::uint64_t> ListTableFiles(Env* env, const std::string& path);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
uint64_t GetNumberOfSstFilesForColumnFamily(DB* db,
|
||||
std::string column_family_name);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
uint64_t TestGetTickerCount(const Options& options, Tickers ticker_type) {
|
||||
return options.statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -14,11 +14,6 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static uint64_t TestGetTickerCount(const Options& options,
|
||||
Tickers ticker_type) {
|
||||
return options.statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
|
||||
static std::string CompressibleString(Random* rnd, int len) {
|
||||
std::string r;
|
||||
test::CompressibleString(rnd, 0.8, len, &r);
|
||||
@@ -960,7 +955,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) {
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.write_buffer_size = 110 << 10; // 105KB
|
||||
options.write_buffer_size = 111 << 10; // 114KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 1;
|
||||
@@ -1091,6 +1086,7 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
// each value is 10K
|
||||
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
@@ -1108,6 +1104,7 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
// each value is 10K
|
||||
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
@@ -1139,6 +1136,7 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
// each value is 10K
|
||||
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
@@ -1155,8 +1153,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) {
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 1024 * 1024 * 1024);
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.write_buffer_size = 110 << 10; // 105KB
|
||||
options.arena_block_size = 4 * 1024;
|
||||
options.write_buffer_size = 111 << 10; // 114KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 1;
|
||||
|
||||
@@ -127,6 +127,637 @@ TEST_F(DBWALTest, SyncWALNotWaitWrite) {
|
||||
ASSERT_EQ(Get("foo2"), "bar2");
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, Recover) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "baz", "v5"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
ASSERT_EQ("v5", Get(1, "baz"));
|
||||
ASSERT_OK(Put(1, "bar", "v2"));
|
||||
ASSERT_OK(Put(1, "foo", "v3"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_EQ("v3", Get(1, "foo"));
|
||||
ASSERT_OK(Put(1, "foo", "v4"));
|
||||
ASSERT_EQ("v4", Get(1, "foo"));
|
||||
ASSERT_EQ("v2", Get(1, "bar"));
|
||||
ASSERT_EQ("v5", Get(1, "baz"));
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithTableHandle) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "bar", "v2"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "foo", "v3"));
|
||||
ASSERT_OK(Put(1, "bar", "v4"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "big", std::string(100, 'a')));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &files);
|
||||
size_t total_files = 0;
|
||||
for (const auto& level : files) {
|
||||
total_files += level.size();
|
||||
}
|
||||
ASSERT_EQ(total_files, 3);
|
||||
for (const auto& level : files) {
|
||||
for (const auto& file : level) {
|
||||
if (kInfiniteMaxOpenFiles == option_config_) {
|
||||
ASSERT_TRUE(file.table_reader_handle != nullptr);
|
||||
} else {
|
||||
ASSERT_TRUE(file.table_reader_handle == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, IgnoreRecoveredLog) {
|
||||
std::string backup_logs = dbname_ + "/backup_logs";
|
||||
|
||||
// delete old files in backup_logs directory
|
||||
env_->CreateDirIfMissing(backup_logs);
|
||||
std::vector<std::string> old_files;
|
||||
env_->GetChildren(backup_logs, &old_files);
|
||||
for (auto& file : old_files) {
|
||||
if (file != "." && file != "..") {
|
||||
env_->DeleteFile(backup_logs + "/" + file);
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
options.wal_dir = dbname_ + "/logs";
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// fill up the DB
|
||||
std::string one, two;
|
||||
PutFixed64(&one, 1);
|
||||
PutFixed64(&two, 2);
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one)));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one)));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Slice("bar"), Slice(one)));
|
||||
|
||||
// copy the logs to backup
|
||||
std::vector<std::string> logs;
|
||||
env_->GetChildren(options.wal_dir, &logs);
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(options.wal_dir + "/" + log, backup_logs + "/" + log);
|
||||
}
|
||||
}
|
||||
|
||||
// recover the DB
|
||||
Reopen(options);
|
||||
ASSERT_EQ(two, Get("foo"));
|
||||
ASSERT_EQ(one, Get("bar"));
|
||||
Close();
|
||||
|
||||
// copy the logs from backup back to wal dir
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
||||
}
|
||||
}
|
||||
// this should ignore the log files, recovery should not happen again
|
||||
// if the recovery happens, the same merge operator would be called twice,
|
||||
// leading to incorrect results
|
||||
Reopen(options);
|
||||
ASSERT_EQ(two, Get("foo"));
|
||||
ASSERT_EQ(one, Get("bar"));
|
||||
Close();
|
||||
Destroy(options);
|
||||
Reopen(options);
|
||||
Close();
|
||||
|
||||
// copy the logs from backup back to wal dir
|
||||
env_->CreateDirIfMissing(options.wal_dir);
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
||||
}
|
||||
}
|
||||
// assert that we successfully recovered only from logs, even though we
|
||||
// destroyed the DB
|
||||
Reopen(options);
|
||||
ASSERT_EQ(two, Get("foo"));
|
||||
ASSERT_EQ(one, Get("bar"));
|
||||
|
||||
// Recovery will fail if DB directory doesn't exist.
|
||||
Destroy(options);
|
||||
// copy the logs from backup back to wal dir
|
||||
env_->CreateDirIfMissing(options.wal_dir);
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
||||
// we won't be needing this file no more
|
||||
env_->DeleteFile(backup_logs + "/" + log);
|
||||
}
|
||||
}
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_TRUE(!s.ok());
|
||||
} while (ChangeOptions(kSkipHashCuckoo));
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoveryWithEmptyLog) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "foo", "v2"));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_OK(Put(1, "foo", "v3"));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_EQ("v3", Get(1, "foo"));
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBWALTest, RecoverWithLargeLog) {
|
||||
do {
|
||||
{
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_OK(Put(1, "big1", std::string(200000, '1')));
|
||||
ASSERT_OK(Put(1, "big2", std::string(200000, '2')));
|
||||
ASSERT_OK(Put(1, "small3", std::string(10, '3')));
|
||||
ASSERT_OK(Put(1, "small4", std::string(10, '4')));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
}
|
||||
|
||||
// Make sure that if we re-open with a small write buffer size that
|
||||
// we flush table files in the middle of a large log file.
|
||||
Options options;
|
||||
options.write_buffer_size = 100000;
|
||||
options = CurrentOptions(options);
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 3);
|
||||
ASSERT_EQ(std::string(200000, '1'), Get(1, "big1"));
|
||||
ASSERT_EQ(std::string(200000, '2'), Get(1, "big2"));
|
||||
ASSERT_EQ(std::string(10, '3'), Get(1, "small3"));
|
||||
ASSERT_EQ(std::string(10, '4'), Get(1, "small4"));
|
||||
ASSERT_GT(NumTableFilesAtLevel(0, 1), 1);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
// In https://reviews.facebook.net/D20661 we change
|
||||
// recovery behavior: previously for each log file each column family
|
||||
// memtable was flushed, even it was empty. Now it's changed:
|
||||
// we try to create the smallest number of table files by merging
|
||||
// updates from multiple logs
|
||||
TEST_F(DBWALTest, RecoverCheckFileAmountWithSmallWriteBuffer) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 5000000;
|
||||
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
||||
|
||||
// Since we will reopen DB with smaller write_buffer_size,
|
||||
// each key will go to new SST file
|
||||
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
||||
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
||||
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
||||
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
||||
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
||||
// Make 'dobrynia' to be flushed and new WAL file to be created
|
||||
ASSERT_OK(Put(2, Key(10), DummyString(7500000)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
{
|
||||
auto tables = ListTableFiles(env_, dbname_);
|
||||
ASSERT_EQ(tables.size(), static_cast<size_t>(1));
|
||||
// Make sure 'dobrynia' was flushed: check sst files amount
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
// New WAL file
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
||||
|
||||
options.write_buffer_size = 4096;
|
||||
options.arena_block_size = 4096;
|
||||
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
||||
options);
|
||||
{
|
||||
// No inserts => default is empty
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
// First 4 keys goes to separate SSTs + 1 more SST for 2 smaller keys
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(5));
|
||||
// 1 SST for big key + 1 SST for small one
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(2));
|
||||
// 1 SST for all keys
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
}
|
||||
|
||||
// In https://reviews.facebook.net/D20661 we change
|
||||
// recovery behavior: previously for each log file each column family
|
||||
// memtable was flushed, even it wasn't empty. Now it's changed:
|
||||
// we try to create the smallest number of table files by merging
|
||||
// updates from multiple logs
|
||||
TEST_F(DBWALTest, RecoverCheckFileAmount) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100000;
|
||||
options.arena_block_size = 4 * 1024;
|
||||
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
||||
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
|
||||
// Make 'nikitich' memtable to be flushed
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1002400)));
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
// 4 memtable are not flushed, 1 sst file
|
||||
{
|
||||
auto tables = ListTableFiles(env_, dbname_);
|
||||
ASSERT_EQ(tables.size(), static_cast<size_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
// Memtable for 'nikitich' has flushed, new WAL file has opened
|
||||
// 4 memtable still not flushed
|
||||
|
||||
// Write to new WAL file
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
|
||||
// Fill up 'nikitich' one more time
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1002400)));
|
||||
// make it flush
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
// There are still 4 memtable not flushed, and 2 sst tables
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
|
||||
{
|
||||
auto tables = ListTableFiles(env_, dbname_);
|
||||
ASSERT_EQ(tables.size(), static_cast<size_t>(2));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
||||
options);
|
||||
{
|
||||
std::vector<uint64_t> table_files = ListTableFiles(env_, dbname_);
|
||||
// Check, that records for 'default', 'dobrynia' and 'pikachu' from
|
||||
// first, second and third WALs went to the same SST.
|
||||
// So, there is 6 SSTs: three for 'nikitich', one for 'default', one for
|
||||
// 'dobrynia', one for 'pikachu'
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(3));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, SyncMultipleLogs) {
|
||||
const uint64_t kNumBatches = 2;
|
||||
const int kBatchSize = 1000;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.write_buffer_size = 4096;
|
||||
Reopen(options);
|
||||
|
||||
WriteBatch batch;
|
||||
WriteOptions wo;
|
||||
wo.sync = true;
|
||||
|
||||
for (uint64_t b = 0; b < kNumBatches; b++) {
|
||||
batch.Clear();
|
||||
for (int i = 0; i < kBatchSize; i++) {
|
||||
batch.Put(Key(i), DummyString(128));
|
||||
}
|
||||
|
||||
dbfull()->Write(wo, &batch);
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SyncWAL());
|
||||
}
|
||||
|
||||
//
|
||||
// Test WAL recovery for the various modes available
|
||||
//
|
||||
class RecoveryTestHelper {
|
||||
public:
|
||||
// Number of WAL files to generate
|
||||
static const int kWALFilesCount = 10;
|
||||
// Starting number for the WAL file name like 00010.log
|
||||
static const int kWALFileOffset = 10;
|
||||
// Keys to be written per WAL file
|
||||
static const int kKeysPerWALFile = 1024;
|
||||
// Size of the value
|
||||
static const int kValueSize = 10;
|
||||
|
||||
// Create WAL files with values filled in
|
||||
static void FillData(DBWALTest* test, const Options& options,
|
||||
const size_t wal_count, size_t* count) {
|
||||
const DBOptions& db_options = options;
|
||||
|
||||
*count = 0;
|
||||
|
||||
shared_ptr<Cache> table_cache = NewLRUCache(50000, 16);
|
||||
EnvOptions env_options;
|
||||
WriteBuffer write_buffer(db_options.db_write_buffer_size);
|
||||
|
||||
unique_ptr<VersionSet> versions;
|
||||
unique_ptr<WalManager> wal_manager;
|
||||
WriteController write_controller;
|
||||
|
||||
versions.reset(new VersionSet(test->dbname_, &db_options, env_options,
|
||||
table_cache.get(), &write_buffer,
|
||||
&write_controller));
|
||||
|
||||
wal_manager.reset(new WalManager(db_options, env_options));
|
||||
|
||||
std::unique_ptr<log::Writer> current_log_writer;
|
||||
|
||||
for (size_t j = kWALFileOffset; j < wal_count + kWALFileOffset; j++) {
|
||||
uint64_t current_log_number = j;
|
||||
std::string fname = LogFileName(test->dbname_, current_log_number);
|
||||
unique_ptr<WritableFile> file;
|
||||
ASSERT_OK(db_options.env->NewWritableFile(fname, &file, env_options));
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options));
|
||||
current_log_writer.reset(
|
||||
new log::Writer(std::move(file_writer), current_log_number,
|
||||
db_options.recycle_log_file_num > 0));
|
||||
|
||||
for (int i = 0; i < kKeysPerWALFile; i++) {
|
||||
std::string key = "key" + ToString((*count)++);
|
||||
std::string value = test->DummyString(kValueSize);
|
||||
assert(current_log_writer.get() != nullptr);
|
||||
uint64_t seq = versions->LastSequence() + 1;
|
||||
WriteBatch batch;
|
||||
batch.Put(key, value);
|
||||
WriteBatchInternal::SetSequence(&batch, seq);
|
||||
current_log_writer->AddRecord(WriteBatchInternal::Contents(&batch));
|
||||
versions->SetLastSequence(seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate and fill the store with some data
|
||||
static size_t FillData(DBWALTest* test, Options* options) {
|
||||
options->create_if_missing = true;
|
||||
test->DestroyAndReopen(*options);
|
||||
test->Close();
|
||||
|
||||
size_t count = 0;
|
||||
FillData(test, *options, kWALFilesCount, &count);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Read back all the keys we wrote and return the number of keys found
|
||||
static size_t GetData(DBWALTest* test) {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < kWALFilesCount * kKeysPerWALFile; i++) {
|
||||
if (test->Get("key" + ToString(i)) != "NOT_FOUND") {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Manuall corrupt the specified WAL
|
||||
static void CorruptWAL(DBWALTest* test, const Options& options,
|
||||
const double off, const double len,
|
||||
const int wal_file_id, const bool trunc = false) {
|
||||
Env* env = options.env;
|
||||
std::string fname = LogFileName(test->dbname_, wal_file_id);
|
||||
uint64_t size;
|
||||
ASSERT_OK(env->GetFileSize(fname, &size));
|
||||
ASSERT_GT(size, 0);
|
||||
#ifdef OS_WIN
|
||||
// Windows disk cache behaves differently. When we truncate
|
||||
// the original content is still in the cache due to the original
|
||||
// handle is still open. Generally, in Windows, one prohibits
|
||||
// shared access to files and it is not needed for WAL but we allow
|
||||
// it to induce corruption at various tests.
|
||||
test->Close();
|
||||
#endif
|
||||
if (trunc) {
|
||||
ASSERT_EQ(0, truncate(fname.c_str(), static_cast<int64_t>(size * off)));
|
||||
} else {
|
||||
InduceCorruption(fname, static_cast<size_t>(size * off),
|
||||
static_cast<size_t>(size * len));
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite data with 'a' from offset for length len
|
||||
static void InduceCorruption(const std::string& filename, size_t offset,
|
||||
size_t len) {
|
||||
ASSERT_GT(len, 0U);
|
||||
|
||||
int fd = open(filename.c_str(), O_RDWR);
|
||||
|
||||
// On windows long is 32-bit
|
||||
ASSERT_LE(offset, std::numeric_limits<long>::max());
|
||||
|
||||
ASSERT_GT(fd, 0);
|
||||
ASSERT_EQ(offset, lseek(fd, static_cast<long>(offset), SEEK_SET));
|
||||
|
||||
void* buf = alloca(len);
|
||||
memset(buf, 'a', len);
|
||||
ASSERT_EQ(len, write(fd, buf, static_cast<unsigned int>(len)));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
};
|
||||
|
||||
// Test scope:
|
||||
// - We expect to open the data store when there is incomplete trailing writes
|
||||
// at the end of any of the logs
|
||||
// - We do not expect to open the data store for corruption
|
||||
TEST_F(DBWALTest, kTolerateCorruptedTailRecords) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
|
||||
for (auto trunc : {true, false}) { /* Corruption style */
|
||||
for (int i = 0; i < 4; i++) { /* Corruption offset position */
|
||||
for (int j = jstart; j < jend; j++) { /* WAL file */
|
||||
// Fill data for testing
|
||||
Options options = CurrentOptions();
|
||||
const size_t row_count = RecoveryTestHelper::FillData(this, &options);
|
||||
// test checksum failure or parsing
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, /*wal=*/j, trunc);
|
||||
|
||||
if (trunc) {
|
||||
options.wal_recovery_mode =
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
const size_t recovered_row_count = RecoveryTestHelper::GetData(this);
|
||||
ASSERT_TRUE(i == 0 || recovered_row_count > 0);
|
||||
ASSERT_LT(recovered_row_count, row_count);
|
||||
} else {
|
||||
options.wal_recovery_mode =
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
ASSERT_NOK(TryReopen(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test scope:
|
||||
// We don't expect the data store to be opened if there is any corruption
|
||||
// (leading, middle or trailing -- incomplete writes or corruption)
|
||||
TEST_F(DBWALTest, kAbsoluteConsistency) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
|
||||
// Verify clean slate behavior
|
||||
Options options = CurrentOptions();
|
||||
const size_t row_count = RecoveryTestHelper::FillData(this, &options);
|
||||
options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
ASSERT_EQ(RecoveryTestHelper::GetData(this), row_count);
|
||||
|
||||
for (auto trunc : {true, false}) { /* Corruption style */
|
||||
for (int i = 0; i < 4; i++) { /* Corruption offset position */
|
||||
if (trunc && i == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = jstart; j < jend; j++) { /* wal files */
|
||||
// fill with new date
|
||||
RecoveryTestHelper::FillData(this, &options);
|
||||
// corrupt the wal
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, j, trunc);
|
||||
// verify
|
||||
options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_NOK(TryReopen(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test scope:
|
||||
// - We expect to open data store under all circumstances
|
||||
// - We expect only data upto the point where the first error was encountered
|
||||
TEST_F(DBWALTest, kPointInTimeRecovery) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
const int maxkeys =
|
||||
RecoveryTestHelper::kWALFilesCount * RecoveryTestHelper::kKeysPerWALFile;
|
||||
|
||||
for (auto trunc : {true, false}) { /* Corruption style */
|
||||
for (int i = 0; i < 4; i++) { /* Offset of corruption */
|
||||
for (int j = jstart; j < jend; j++) { /* WAL file */
|
||||
// Fill data for testing
|
||||
Options options = CurrentOptions();
|
||||
const size_t row_count = RecoveryTestHelper::FillData(this, &options);
|
||||
|
||||
// Corrupt the wal
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, j, trunc);
|
||||
|
||||
// Verify
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// Probe data for invariants
|
||||
size_t recovered_row_count = RecoveryTestHelper::GetData(this);
|
||||
ASSERT_LT(recovered_row_count, row_count);
|
||||
|
||||
bool expect_data = true;
|
||||
for (size_t k = 0; k < maxkeys; ++k) {
|
||||
bool found = Get("key" + ToString(i)) != "NOT_FOUND";
|
||||
if (expect_data && !found) {
|
||||
expect_data = false;
|
||||
}
|
||||
ASSERT_EQ(found, expect_data);
|
||||
}
|
||||
|
||||
const size_t min = RecoveryTestHelper::kKeysPerWALFile *
|
||||
(j - RecoveryTestHelper::kWALFileOffset);
|
||||
ASSERT_GE(recovered_row_count, min);
|
||||
if (!trunc && i != 0) {
|
||||
const size_t max = RecoveryTestHelper::kKeysPerWALFile *
|
||||
(j - RecoveryTestHelper::kWALFileOffset + 1);
|
||||
ASSERT_LE(recovered_row_count, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test scope:
|
||||
// - We expect to open the data store under all scenarios
|
||||
// - We expect to have recovered records past the corruption zone
|
||||
TEST_F(DBWALTest, kSkipAnyCorruptedRecords) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
|
||||
for (auto trunc : {true, false}) { /* Corruption style */
|
||||
for (int i = 0; i < 4; i++) { /* Corruption offset */
|
||||
for (int j = jstart; j < jend; j++) { /* wal files */
|
||||
// Fill data for testing
|
||||
Options options = CurrentOptions();
|
||||
const size_t row_count = RecoveryTestHelper::FillData(this, &options);
|
||||
|
||||
// Corrupt the WAL
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, j, trunc);
|
||||
|
||||
// Verify behavior
|
||||
options.wal_recovery_mode = WALRecoveryMode::kSkipAnyCorruptedRecords;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// Probe data for invariants
|
||||
size_t recovered_row_count = RecoveryTestHelper::GetData(this);
|
||||
ASSERT_LT(recovered_row_count, row_count);
|
||||
|
||||
if (!trunc) {
|
||||
ASSERT_TRUE(i != 0 || recovered_row_count > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -92,6 +92,30 @@ TEST_F(FormatTest, InternalKeyShortSeparator) {
|
||||
Shorten(IKey("foo", 100, kTypeValue),
|
||||
IKey("hello", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(IKey("ABC2", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("ABC1AAAAA", 100, kTypeValue),
|
||||
IKey("ABC2ABB", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("AAA1AAA", 100, kTypeValue),
|
||||
IKey("AAA2AA", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(
|
||||
IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("AAA1AAA", 100, kTypeValue), IKey("AAA4", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(
|
||||
IKey("AAA1B", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("AAA1AAA", 100, kTypeValue), IKey("AAA2", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("AAA1AAA", 100, kTypeValue),
|
||||
IKey("AAA2A", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(
|
||||
IKey("AAA1", 100, kTypeValue),
|
||||
Shorten(IKey("AAA1", 100, kTypeValue), IKey("AAA2", 200, kTypeValue)));
|
||||
|
||||
// When start user key is prefix of limit user key
|
||||
ASSERT_EQ(IKey("foo", 100, kTypeValue),
|
||||
Shorten(IKey("foo", 100, kTypeValue),
|
||||
|
||||
+64
-38
@@ -20,57 +20,83 @@ void EventHelpers::AppendCurrentTime(JSONWriter* jwriter) {
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
void EventHelpers::LogAndNotifyTableFileCreation(
|
||||
#ifndef ROCKSDB_LITE
|
||||
void EventHelpers::NotifyTableFileCreationStarted(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const std::string& db_name, const std::string& cf_name,
|
||||
const std::string& file_path, int job_id, TableFileCreationReason reason) {
|
||||
TableFileCreationBriefInfo info;
|
||||
info.db_name = db_name;
|
||||
info.cf_name = cf_name;
|
||||
info.file_path = file_path;
|
||||
info.job_id = job_id;
|
||||
info.reason = reason;
|
||||
for (auto& listener : listeners) {
|
||||
listener->OnTableFileCreationStarted(info);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const FileDescriptor& fd, const TableFileCreationInfo& info) {
|
||||
assert(event_logger);
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
jwriter << "cf_name" << info.cf_name
|
||||
<< "job" << info.job_id
|
||||
<< "event" << "table_file_creation"
|
||||
<< "file_number" << fd.GetNumber()
|
||||
<< "file_size" << fd.GetFileSize();
|
||||
const std::string& db_name, const std::string& cf_name,
|
||||
const std::string& file_path, int job_id, const FileDescriptor& fd,
|
||||
const TableProperties& table_properties, TableFileCreationReason reason,
|
||||
const Status& s) {
|
||||
if (s.ok() && event_logger) {
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
jwriter << "cf_name" << cf_name << "job" << job_id << "event"
|
||||
<< "table_file_creation"
|
||||
<< "file_number" << fd.GetNumber() << "file_size"
|
||||
<< fd.GetFileSize();
|
||||
|
||||
// table_properties
|
||||
{
|
||||
jwriter << "table_properties";
|
||||
jwriter.StartObject();
|
||||
// table_properties
|
||||
{
|
||||
jwriter << "table_properties";
|
||||
jwriter.StartObject();
|
||||
|
||||
// basic properties:
|
||||
jwriter << "data_size" << info.table_properties.data_size
|
||||
<< "index_size" << info.table_properties.index_size
|
||||
<< "filter_size" << info.table_properties.filter_size
|
||||
<< "raw_key_size" << info.table_properties.raw_key_size
|
||||
<< "raw_average_key_size" << SafeDivide(
|
||||
info.table_properties.raw_key_size,
|
||||
info.table_properties.num_entries)
|
||||
<< "raw_value_size" << info.table_properties.raw_value_size
|
||||
<< "raw_average_value_size" << SafeDivide(
|
||||
info.table_properties.raw_value_size,
|
||||
info.table_properties.num_entries)
|
||||
<< "num_data_blocks" << info.table_properties.num_data_blocks
|
||||
<< "num_entries" << info.table_properties.num_entries
|
||||
<< "filter_policy_name" <<
|
||||
info.table_properties.filter_policy_name;
|
||||
// basic properties:
|
||||
jwriter << "data_size" << table_properties.data_size << "index_size"
|
||||
<< table_properties.index_size << "filter_size"
|
||||
<< table_properties.filter_size << "raw_key_size"
|
||||
<< table_properties.raw_key_size << "raw_average_key_size"
|
||||
<< SafeDivide(table_properties.raw_key_size,
|
||||
table_properties.num_entries)
|
||||
<< "raw_value_size" << table_properties.raw_value_size
|
||||
<< "raw_average_value_size"
|
||||
<< SafeDivide(table_properties.raw_value_size,
|
||||
table_properties.num_entries)
|
||||
<< "num_data_blocks" << table_properties.num_data_blocks
|
||||
<< "num_entries" << table_properties.num_entries
|
||||
<< "filter_policy_name" << table_properties.filter_policy_name;
|
||||
|
||||
// user collected properties
|
||||
for (const auto& prop : info.table_properties.readable_properties) {
|
||||
jwriter << prop.first << prop.second;
|
||||
// user collected properties
|
||||
for (const auto& prop : table_properties.readable_properties) {
|
||||
jwriter << prop.first << prop.second;
|
||||
}
|
||||
jwriter.EndObject();
|
||||
}
|
||||
jwriter.EndObject();
|
||||
}
|
||||
jwriter.EndObject();
|
||||
|
||||
event_logger->Log(jwriter);
|
||||
event_logger->Log(jwriter);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (listeners.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto listener : listeners) {
|
||||
TableFileCreationInfo info;
|
||||
info.db_name = db_name;
|
||||
info.cf_name = cf_name;
|
||||
info.file_path = file_path;
|
||||
info.file_size = fd.file_size;
|
||||
info.job_id = job_id;
|
||||
info.table_properties = table_properties;
|
||||
info.reason = reason;
|
||||
info.status = s;
|
||||
for (auto& listener : listeners) {
|
||||
listener->OnTableFileCreated(info);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
+17
-2
@@ -19,15 +19,30 @@ namespace rocksdb {
|
||||
class EventHelpers {
|
||||
public:
|
||||
static void AppendCurrentTime(JSONWriter* json_writer);
|
||||
static void LogAndNotifyTableFileCreation(
|
||||
#ifndef ROCKSDB_LITE
|
||||
static void NotifyTableFileCreationStarted(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const std::string& db_name, const std::string& cf_name,
|
||||
const std::string& file_path, int job_id, TableFileCreationReason reason);
|
||||
#endif // !ROCKSDB_LITE
|
||||
static void LogAndNotifyTableFileCreationFinished(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const FileDescriptor& fd, const TableFileCreationInfo& info);
|
||||
const std::string& db_name, const std::string& cf_name,
|
||||
const std::string& file_path, int job_id, const FileDescriptor& fd,
|
||||
const TableProperties& table_properties, TableFileCreationReason reason,
|
||||
const Status& s);
|
||||
static void LogAndNotifyTableFileDeletion(
|
||||
EventLogger* event_logger, int job_id,
|
||||
uint64_t file_number, const std::string& file_path,
|
||||
const Status& status, const std::string& db_name,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners);
|
||||
|
||||
private:
|
||||
static void LogAndNotifyTableFileCreation(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const FileDescriptor& fd, const TableFileCreationInfo& info);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+36
-21
@@ -66,7 +66,7 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger)
|
||||
EventLogger* event_logger, bool measure_io_stats)
|
||||
: dbname_(dbname),
|
||||
cfd_(cfd),
|
||||
db_options_(db_options),
|
||||
@@ -83,7 +83,8 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
output_file_directory_(output_file_directory),
|
||||
output_compression_(output_compression),
|
||||
stats_(stats),
|
||||
event_logger_(event_logger) {
|
||||
event_logger_(event_logger),
|
||||
measure_io_stats_(measure_io_stats) {
|
||||
// Update the thread status to indicate flush.
|
||||
ReportStartedFlush();
|
||||
TEST_SYNC_POINT("FlushJob::FlushJob()");
|
||||
@@ -114,13 +115,30 @@ void FlushJob::ReportFlushInputSize(const autovector<MemTable*>& mems) {
|
||||
}
|
||||
|
||||
void FlushJob::RecordFlushIOStats() {
|
||||
ThreadStatusUtil::SetThreadOperationProperty(
|
||||
RecordTick(stats_, FLUSH_WRITE_BYTES, IOSTATS(bytes_written));
|
||||
ThreadStatusUtil::IncreaseThreadOperationProperty(
|
||||
ThreadStatus::FLUSH_BYTES_WRITTEN, IOSTATS(bytes_written));
|
||||
IOSTATS_RESET(bytes_written);
|
||||
}
|
||||
|
||||
Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
AutoThreadOperationStageUpdater stage_run(
|
||||
ThreadStatus::STAGE_FLUSH_RUN);
|
||||
// I/O measurement variables
|
||||
PerfLevel prev_perf_level = PerfLevel::kEnableTime;
|
||||
uint64_t prev_write_nanos = 0;
|
||||
uint64_t prev_fsync_nanos = 0;
|
||||
uint64_t prev_range_sync_nanos = 0;
|
||||
uint64_t prev_prepare_write_nanos = 0;
|
||||
if (measure_io_stats_) {
|
||||
prev_perf_level = GetPerfLevel();
|
||||
SetPerfLevel(PerfLevel::kEnableTime);
|
||||
prev_write_nanos = IOSTATS(write_nanos);
|
||||
prev_fsync_nanos = IOSTATS(fsync_nanos);
|
||||
prev_range_sync_nanos = IOSTATS(range_sync_nanos);
|
||||
prev_prepare_write_nanos = IOSTATS(prepare_write_nanos);
|
||||
}
|
||||
|
||||
// Save the contents of the earliest memtable as a new Table
|
||||
FileMetaData meta;
|
||||
autovector<MemTable*> mems;
|
||||
@@ -180,6 +198,18 @@ Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
}
|
||||
stream.EndArray();
|
||||
|
||||
if (measure_io_stats_) {
|
||||
if (prev_perf_level != PerfLevel::kEnableTime) {
|
||||
SetPerfLevel(prev_perf_level);
|
||||
}
|
||||
stream << "file_write_nanos" << (IOSTATS(write_nanos) - prev_write_nanos);
|
||||
stream << "file_range_sync_nanos"
|
||||
<< (IOSTATS(range_sync_nanos) - prev_range_sync_nanos);
|
||||
stream << "file_fsync_nanos" << (IOSTATS(fsync_nanos) - prev_fsync_nanos);
|
||||
stream << "file_prepare_write_nanos"
|
||||
<< (IOSTATS(prepare_write_nanos) - prev_prepare_write_nanos);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -223,7 +253,6 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
<< total_num_deletes << "memory_usage"
|
||||
<< total_memory_usage;
|
||||
|
||||
TableFileCreationInfo info;
|
||||
{
|
||||
ScopedArenaIterator iter(
|
||||
NewMergingIterator(&cfd_->internal_comparator(), &memtables[0],
|
||||
@@ -242,8 +271,8 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
earliest_write_conflict_snapshot_, output_compression_,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
TableFileCreationReason::kFlush, event_logger_, job_context_->job_id,
|
||||
Env::IO_HIGH, &table_properties_, 0 /* level */);
|
||||
info.table_properties = table_properties_;
|
||||
LogFlush(db_options_.info_log);
|
||||
}
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
@@ -254,21 +283,6 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
meta->fd.GetFileSize(), s.ToString().c_str(),
|
||||
meta->marked_for_compaction ? " (needs compaction)" : "");
|
||||
|
||||
// output to event logger
|
||||
if (s.ok()) {
|
||||
info.db_name = dbname_;
|
||||
info.cf_name = cfd_->GetName();
|
||||
info.file_path = TableFileName(db_options_.db_paths,
|
||||
meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
info.file_size = meta->fd.GetFileSize();
|
||||
info.job_id = job_context_->job_id;
|
||||
EventHelpers::LogAndNotifyTableFileCreation(
|
||||
event_logger_, db_options_.listeners,
|
||||
meta->fd, info);
|
||||
TEST_SYNC_POINT("FlushJob::LogAndNotifyTableFileCreation()");
|
||||
}
|
||||
|
||||
if (!db_options_.disableDataSync && output_file_directory_ != nullptr) {
|
||||
output_file_directory_->Fsync();
|
||||
}
|
||||
@@ -294,13 +308,14 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
meta->marked_for_compaction);
|
||||
}
|
||||
|
||||
// Note that here we treat flush as level 0 compaction in internal stats
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = db_options_.env->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta->fd.GetFileSize();
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta->fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta->fd.GetFileSize());
|
||||
RecordFlushIOStats();
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -62,7 +62,7 @@ class FlushJob {
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger);
|
||||
EventLogger* event_logger, bool measure_io_stats);
|
||||
|
||||
~FlushJob();
|
||||
|
||||
@@ -93,6 +93,7 @@ class FlushJob {
|
||||
Statistics* stats_;
|
||||
EventLogger* event_logger_;
|
||||
TableProperties table_properties_;
|
||||
bool measure_io_stats_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -93,7 +93,7 @@ TEST_F(FlushJobTest, Empty) {
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
{}, kMaxSequenceNumber, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger);
|
||||
nullptr, kNoCompression, nullptr, &event_logger, false);
|
||||
ASSERT_OK(flush_job.Run());
|
||||
job_context.Clean();
|
||||
}
|
||||
@@ -132,7 +132,7 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
{}, kMaxSequenceNumber, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger);
|
||||
nullptr, kNoCompression, nullptr, &event_logger, true);
|
||||
FileMetaData fd;
|
||||
mutex_.Lock();
|
||||
ASSERT_OK(flush_job.Run(&fd));
|
||||
@@ -192,11 +192,11 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
snapshots, kMaxSequenceNumber, &job_context, nullptr,
|
||||
nullptr, nullptr, kNoCompression, nullptr, &event_logger);
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), env_options_, versions_.get(), &mutex_,
|
||||
&shutting_down_, snapshots, kMaxSequenceNumber, &job_context, nullptr,
|
||||
nullptr, nullptr, kNoCompression, nullptr, &event_logger, true);
|
||||
mutex_.Lock();
|
||||
ASSERT_OK(flush_job.Run());
|
||||
mutex_.Unlock();
|
||||
|
||||
+67
-39
@@ -101,6 +101,8 @@ std::pair<Slice, Slice> GetPropertyNameAndArg(const Slice& property) {
|
||||
static const std::string rocksdb_prefix = "rocksdb.";
|
||||
|
||||
static const std::string num_files_at_level_prefix = "num-files-at-level";
|
||||
static const std::string compression_ratio_at_level_prefix =
|
||||
"compression-ratio-at-level";
|
||||
static const std::string allstats = "stats";
|
||||
static const std::string sstables = "sstables";
|
||||
static const std::string cfstats = "cfstats";
|
||||
@@ -148,6 +150,8 @@ static const std::string num_running_flushes = "num-running-flushes";
|
||||
|
||||
const std::string DB::Properties::kNumFilesAtLevelPrefix =
|
||||
rocksdb_prefix + num_files_at_level_prefix;
|
||||
const std::string DB::Properties::kCompressionRatioAtLevelPrefix =
|
||||
rocksdb_prefix + compression_ratio_at_level_prefix;
|
||||
const std::string DB::Properties::kStats = rocksdb_prefix + allstats;
|
||||
const std::string DB::Properties::kSSTables = rocksdb_prefix + sstables;
|
||||
const std::string DB::Properties::kCFStats = rocksdb_prefix + cfstats;
|
||||
@@ -211,6 +215,8 @@ const std::unordered_map<std::string,
|
||||
DBPropertyInfo> InternalStats::ppt_name_to_info = {
|
||||
{DB::Properties::kNumFilesAtLevelPrefix,
|
||||
{false, &InternalStats::HandleNumFilesAtLevel, nullptr}},
|
||||
{DB::Properties::kCompressionRatioAtLevelPrefix,
|
||||
{false, &InternalStats::HandleCompressionRatioAtLevelPrefix, nullptr}},
|
||||
{DB::Properties::kLevelStats,
|
||||
{false, &InternalStats::HandleLevelStats, nullptr}},
|
||||
{DB::Properties::kStats, {false, &InternalStats::HandleStats, nullptr}},
|
||||
@@ -324,6 +330,19 @@ bool InternalStats::HandleNumFilesAtLevel(std::string* value, Slice suffix) {
|
||||
}
|
||||
}
|
||||
|
||||
bool InternalStats::HandleCompressionRatioAtLevelPrefix(std::string* value,
|
||||
Slice suffix) {
|
||||
uint64_t level;
|
||||
const auto* vstorage = cfd_->current()->storage_info();
|
||||
bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
|
||||
if (!ok || level >= static_cast<uint64_t>(number_levels_)) {
|
||||
return false;
|
||||
}
|
||||
*value = ToString(
|
||||
vstorage->GetEstimatedCompressionRatioAtLevel(static_cast<int>(level)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleLevelStats(std::string* value, Slice suffix) {
|
||||
char buf[1000];
|
||||
const auto* vstorage = cfd_->current()->storage_info();
|
||||
@@ -589,9 +608,6 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
uint64_t wal_synced = GetDBStats(InternalStats::WAL_FILE_SYNCED);
|
||||
uint64_t write_with_wal = GetDBStats(InternalStats::WRITE_WITH_WAL);
|
||||
uint64_t write_stall_micros = GetDBStats(InternalStats::WRITE_STALL_MICROS);
|
||||
uint64_t compact_bytes_read = 0;
|
||||
uint64_t compact_bytes_write = 0;
|
||||
uint64_t compact_micros = 0;
|
||||
|
||||
const int kHumanMicrosLen = 32;
|
||||
char human_micros[kHumanMicrosLen];
|
||||
@@ -623,20 +639,6 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
write_with_wal / static_cast<double>(wal_synced + 1),
|
||||
wal_bytes / kGB, wal_bytes / kMB / seconds_up);
|
||||
value->append(buf);
|
||||
// Compact
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
compact_bytes_read += comp_stats_[level].bytes_read_output_level +
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
compact_bytes_write += comp_stats_[level].bytes_written;
|
||||
compact_micros += comp_stats_[level].micros;
|
||||
}
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Cumulative compaction: %.2f GB write, %.2f MB/s write, "
|
||||
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
||||
compact_bytes_write / kGB, compact_bytes_write / kMB / seconds_up,
|
||||
compact_bytes_read / kGB, compact_bytes_read / kMB / seconds_up,
|
||||
compact_micros / kMicrosInSec);
|
||||
value->append(buf);
|
||||
// Stall
|
||||
AppendHumanMicros(write_stall_micros, human_micros, kHumanMicrosLen, true);
|
||||
snprintf(buf, sizeof(buf),
|
||||
@@ -681,25 +683,6 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
interval_wal_bytes / kMB / std::max(interval_seconds_up, 0.001));
|
||||
value->append(buf);
|
||||
|
||||
// Compaction
|
||||
uint64_t interval_compact_bytes_write =
|
||||
compact_bytes_write - db_stats_snapshot_.compact_bytes_write;
|
||||
uint64_t interval_compact_bytes_read =
|
||||
compact_bytes_read - db_stats_snapshot_.compact_bytes_read;
|
||||
uint64_t interval_compact_micros =
|
||||
compact_micros - db_stats_snapshot_.compact_micros;
|
||||
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"Interval compaction: %.2f GB write, %.2f MB/s write, "
|
||||
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
||||
interval_compact_bytes_write / kGB,
|
||||
interval_compact_bytes_write / kMB / std::max(interval_seconds_up, 0.001),
|
||||
interval_compact_bytes_read / kGB,
|
||||
interval_compact_bytes_read / kMB / std::max(interval_seconds_up, 0.001),
|
||||
interval_compact_micros / kMicrosInSec);
|
||||
value->append(buf);
|
||||
|
||||
// Stall
|
||||
AppendHumanMicros(
|
||||
write_stall_micros - db_stats_snapshot_.write_stall_micros,
|
||||
@@ -731,9 +714,6 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
db_stats_snapshot_.wal_synced = wal_synced;
|
||||
db_stats_snapshot_.write_with_wal = write_with_wal;
|
||||
db_stats_snapshot_.write_stall_micros = write_stall_micros;
|
||||
db_stats_snapshot_.compact_bytes_write = compact_bytes_write;
|
||||
db_stats_snapshot_.compact_bytes_read = compact_bytes_read;
|
||||
db_stats_snapshot_.compact_micros = compact_micros;
|
||||
}
|
||||
|
||||
void InternalStats::DumpCFStats(std::string* value) {
|
||||
@@ -789,6 +769,7 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
value->append(buf);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t curr_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
// Cumulative summary
|
||||
double w_amp = stats_sum.bytes_written / static_cast<double>(curr_ingest + 1);
|
||||
@@ -812,11 +793,58 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0, w_amp, interval_stats);
|
||||
value->append(buf);
|
||||
|
||||
double seconds_up = (env_->NowMicros() - started_at_ + 1) / kMicrosInSec;
|
||||
double interval_seconds_up = seconds_up - cf_stats_snapshot_.seconds_up;
|
||||
snprintf(buf, sizeof(buf), "Uptime(secs): %.1f total, %.1f interval\n",
|
||||
seconds_up, interval_seconds_up);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Flush(GB): cumulative %.3f, interval %.3f\n",
|
||||
curr_ingest / kGB, interval_ingest / kGB);
|
||||
value->append(buf);
|
||||
|
||||
// Compact
|
||||
uint64_t compact_bytes_read = 0;
|
||||
uint64_t compact_bytes_write = 0;
|
||||
uint64_t compact_micros = 0;
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
compact_bytes_read += comp_stats_[level].bytes_read_output_level +
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
compact_bytes_write += comp_stats_[level].bytes_written;
|
||||
compact_micros += comp_stats_[level].micros;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Cumulative compaction: %.2f GB write, %.2f MB/s write, "
|
||||
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
||||
compact_bytes_write / kGB, compact_bytes_write / kMB / seconds_up,
|
||||
compact_bytes_read / kGB, compact_bytes_read / kMB / seconds_up,
|
||||
compact_micros / kMicrosInSec);
|
||||
value->append(buf);
|
||||
|
||||
// Compaction interval
|
||||
uint64_t interval_compact_bytes_write =
|
||||
compact_bytes_write - cf_stats_snapshot_.compact_bytes_write;
|
||||
uint64_t interval_compact_bytes_read =
|
||||
compact_bytes_read - cf_stats_snapshot_.compact_bytes_read;
|
||||
uint64_t interval_compact_micros =
|
||||
compact_micros - cf_stats_snapshot_.compact_micros;
|
||||
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"Interval compaction: %.2f GB write, %.2f MB/s write, "
|
||||
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
||||
interval_compact_bytes_write / kGB,
|
||||
interval_compact_bytes_write / kMB / std::max(interval_seconds_up, 0.001),
|
||||
interval_compact_bytes_read / kGB,
|
||||
interval_compact_bytes_read / kMB / std::max(interval_seconds_up, 0.001),
|
||||
interval_compact_micros / kMicrosInSec);
|
||||
value->append(buf);
|
||||
cf_stats_snapshot_.compact_bytes_write = compact_bytes_write;
|
||||
cf_stats_snapshot_.compact_bytes_read = compact_bytes_read;
|
||||
cf_stats_snapshot_.compact_micros = compact_micros;
|
||||
|
||||
snprintf(buf, sizeof(buf), "Stalls(count): %" PRIu64
|
||||
" level0_slowdown, "
|
||||
"%" PRIu64
|
||||
|
||||
+11
-8
@@ -246,11 +246,20 @@ class InternalStats {
|
||||
CompactionStats comp_stats;
|
||||
uint64_t ingest_bytes; // Bytes written to L0
|
||||
uint64_t stall_count; // Stall count
|
||||
// Stats from compaction jobs - bytes written, bytes read, duration.
|
||||
uint64_t compact_bytes_write;
|
||||
uint64_t compact_bytes_read;
|
||||
uint64_t compact_micros;
|
||||
double seconds_up;
|
||||
|
||||
CFStatsSnapshot()
|
||||
: comp_stats(0),
|
||||
ingest_bytes(0),
|
||||
stall_count(0) {}
|
||||
stall_count(0),
|
||||
compact_bytes_write(0),
|
||||
compact_bytes_read(0),
|
||||
compact_micros(0),
|
||||
seconds_up(0) {}
|
||||
} cf_stats_snapshot_;
|
||||
|
||||
struct DBStatsSnapshot {
|
||||
@@ -263,10 +272,6 @@ class InternalStats {
|
||||
// another thread.
|
||||
uint64_t write_other;
|
||||
uint64_t write_self;
|
||||
// Stats from compaction jobs - bytes written, bytes read, duration.
|
||||
uint64_t compact_bytes_write;
|
||||
uint64_t compact_bytes_read;
|
||||
uint64_t compact_micros;
|
||||
// Total number of keys written. write_self and write_other measure number
|
||||
// of write requests written, Each of the write request can contain updates
|
||||
// to multiple keys. num_keys_written is total number of keys updated by all
|
||||
@@ -283,9 +288,6 @@ class InternalStats {
|
||||
write_with_wal(0),
|
||||
write_other(0),
|
||||
write_self(0),
|
||||
compact_bytes_write(0),
|
||||
compact_bytes_read(0),
|
||||
compact_micros(0),
|
||||
num_keys_written(0),
|
||||
write_stall_micros(0),
|
||||
seconds_up(0) {}
|
||||
@@ -294,6 +296,7 @@ class InternalStats {
|
||||
// Handler functions for getting property values. They use "value" as a value-
|
||||
// result argument, and return true upon successfully setting "value".
|
||||
bool HandleNumFilesAtLevel(std::string* value, Slice suffix);
|
||||
bool HandleCompressionRatioAtLevelPrefix(std::string* value, Slice suffix);
|
||||
bool HandleLevelStats(std::string* value, Slice suffix);
|
||||
bool HandleStats(std::string* value, Slice suffix);
|
||||
bool HandleCFStats(std::string* value, Slice suffix);
|
||||
|
||||
+145
-1
@@ -484,7 +484,10 @@ TEST_F(EventListenerTest, CompactionReasonLevel) {
|
||||
listener->compaction_reasons_.clear();
|
||||
Reopen(options);
|
||||
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
Put("key", "value");
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kManualCompaction);
|
||||
@@ -579,6 +582,147 @@ TEST_F(EventListenerTest, CompactionReasonFIFO) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kFIFOMaxSize);
|
||||
}
|
||||
}
|
||||
|
||||
class TableFileCreationListener : public EventListener {
|
||||
public:
|
||||
class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
TestEnv() : EnvWrapper(Env::Default()) {}
|
||||
|
||||
void SetStatus(Status s) { status_ = s; }
|
||||
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) {
|
||||
if (fname.size() > 4 && fname.substr(fname.size() - 4) == ".sst") {
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
return Env::Default()->NewWritableFile(fname, result, options);
|
||||
}
|
||||
|
||||
private:
|
||||
Status status_;
|
||||
};
|
||||
|
||||
TableFileCreationListener() {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
started_[i] = finished_[i] = failure_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Index(TableFileCreationReason reason) {
|
||||
int idx;
|
||||
switch (reason) {
|
||||
case TableFileCreationReason::kFlush:
|
||||
idx = 0;
|
||||
break;
|
||||
case TableFileCreationReason::kCompaction:
|
||||
idx = 1;
|
||||
break;
|
||||
default:
|
||||
idx = -1;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
void CheckAndResetCounters(int flush_started, int flush_finished,
|
||||
int flush_failure, int compaction_started,
|
||||
int compaction_finished, int compaction_failure) {
|
||||
ASSERT_EQ(started_[0], flush_started);
|
||||
ASSERT_EQ(finished_[0], flush_finished);
|
||||
ASSERT_EQ(failure_[0], flush_failure);
|
||||
ASSERT_EQ(started_[1], compaction_started);
|
||||
ASSERT_EQ(finished_[1], compaction_finished);
|
||||
ASSERT_EQ(failure_[1], compaction_failure);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
started_[i] = finished_[i] = failure_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTableFileCreationStarted(
|
||||
const TableFileCreationBriefInfo& info) override {
|
||||
int idx = Index(info.reason);
|
||||
if (idx >= 0) {
|
||||
started_[idx]++;
|
||||
}
|
||||
ASSERT_GT(info.db_name.size(), 0U);
|
||||
ASSERT_GT(info.cf_name.size(), 0U);
|
||||
ASSERT_GT(info.file_path.size(), 0U);
|
||||
ASSERT_GT(info.job_id, 0);
|
||||
}
|
||||
|
||||
void OnTableFileCreated(const TableFileCreationInfo& info) override {
|
||||
int idx = Index(info.reason);
|
||||
if (idx >= 0) {
|
||||
finished_[idx]++;
|
||||
}
|
||||
ASSERT_GT(info.db_name.size(), 0U);
|
||||
ASSERT_GT(info.cf_name.size(), 0U);
|
||||
ASSERT_GT(info.file_path.size(), 0U);
|
||||
ASSERT_GT(info.job_id, 0);
|
||||
if (info.status.ok()) {
|
||||
ASSERT_GT(info.table_properties.data_size, 0U);
|
||||
ASSERT_GT(info.table_properties.raw_key_size, 0U);
|
||||
ASSERT_GT(info.table_properties.raw_value_size, 0U);
|
||||
ASSERT_GT(info.table_properties.num_data_blocks, 0U);
|
||||
ASSERT_GT(info.table_properties.num_entries, 0U);
|
||||
} else {
|
||||
if (idx >= 0) {
|
||||
failure_[idx]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TestEnv test_env;
|
||||
int started_[2];
|
||||
int finished_[2];
|
||||
int failure_[2];
|
||||
};
|
||||
|
||||
TEST_F(EventListenerTest, TableFileCreationListenersTest) {
|
||||
auto listener = std::make_shared<TableFileCreationListener>();
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.listeners.push_back(listener);
|
||||
options.env = &listener->test_env;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "aaa"));
|
||||
ASSERT_OK(Put("bar", "bbb"));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
|
||||
|
||||
ASSERT_OK(Put("foo", "aaa1"));
|
||||
ASSERT_OK(Put("bar", "bbb1"));
|
||||
listener->test_env.SetStatus(Status::NotSupported("not supported"));
|
||||
ASSERT_NOK(Flush());
|
||||
listener->CheckAndResetCounters(1, 1, 1, 0, 0, 0);
|
||||
listener->test_env.SetStatus(Status::OK());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "aaa2"));
|
||||
ASSERT_OK(Put("bar", "bbb2"));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
|
||||
|
||||
const Slice kRangeStart = "a";
|
||||
const Slice kRangeEnd = "z";
|
||||
dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
listener->CheckAndResetCounters(0, 0, 0, 1, 1, 0);
|
||||
|
||||
ASSERT_OK(Put("foo", "aaa3"));
|
||||
ASSERT_OK(Put("bar", "bbb3"));
|
||||
ASSERT_OK(Flush());
|
||||
listener->test_env.SetStatus(Status::NotSupported("not supported"));
|
||||
dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 1, 1, 1);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
+15
-10
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -231,6 +232,12 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
|
||||
~MemTableIterator() {
|
||||
#ifndef NDEBUG
|
||||
// Assert that the MemTableIterator is never deleted while
|
||||
// Pinning is Enabled.
|
||||
assert(!pinned_iters_mgr_ ||
|
||||
(pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled()));
|
||||
#endif
|
||||
if (arena_mode_) {
|
||||
iter_->~Iterator();
|
||||
} else {
|
||||
@@ -238,6 +245,14 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
virtual void SetPinnedItersMgr(
|
||||
PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
pinned_iters_mgr_ = pinned_iters_mgr;
|
||||
}
|
||||
PinnedIteratorsManager* pinned_iters_mgr_ = nullptr;
|
||||
#endif
|
||||
|
||||
virtual bool Valid() const override { return valid_; }
|
||||
virtual void Seek(const Slice& k) override {
|
||||
PERF_TIMER_GUARD(seek_on_memtable_time);
|
||||
@@ -285,16 +300,6 @@ class MemTableIterator : public InternalIterator {
|
||||
|
||||
virtual Status status() const override { return Status::OK(); }
|
||||
|
||||
virtual Status PinData() override {
|
||||
// memtable data is always pinned
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status ReleasePinnedData() override {
|
||||
// memtable data is always pinned
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual bool IsKeyPinned() const override {
|
||||
// memtable data is always pinned
|
||||
return true;
|
||||
|
||||
+15
-4
@@ -37,6 +37,7 @@ std::shared_ptr<DB> OpenDb(bool read_only = false) {
|
||||
DB* db;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.max_open_files = -1;
|
||||
options.write_buffer_size = FLAGS_write_buffer_size;
|
||||
options.max_write_buffer_number = FLAGS_max_write_buffer_number;
|
||||
options.min_write_buffer_number_to_merge =
|
||||
@@ -279,14 +280,19 @@ void ProfileQueries(bool enabled_time = false) {
|
||||
#endif
|
||||
|
||||
for (const int i : keys) {
|
||||
if (i == kFlushFlag) {
|
||||
continue;
|
||||
}
|
||||
std::string key = "k" + ToString(i);
|
||||
std::string value = "v" + ToString(i);
|
||||
std::string expected_value = "v" + ToString(i);
|
||||
std::string value;
|
||||
|
||||
std::vector<Slice> multiget_keys = {Slice(key)};
|
||||
std::vector<std::string> values;
|
||||
|
||||
perf_context.Reset();
|
||||
db->Get(read_options, key, &value);
|
||||
ASSERT_OK(db->Get(read_options, key, &value));
|
||||
ASSERT_EQ(expected_value, value);
|
||||
hist_get_snapshot.Add(perf_context.get_snapshot_time);
|
||||
hist_get_memtable.Add(perf_context.get_from_memtable_time);
|
||||
hist_get_files.Add(perf_context.get_from_output_files_time);
|
||||
@@ -375,14 +381,19 @@ void ProfileQueries(bool enabled_time = false) {
|
||||
hist_mget_num_memtable_checked.Clear();
|
||||
|
||||
for (const int i : keys) {
|
||||
if (i == kFlushFlag) {
|
||||
continue;
|
||||
}
|
||||
std::string key = "k" + ToString(i);
|
||||
std::string value = "v" + ToString(i);
|
||||
std::string expected_value = "v" + ToString(i);
|
||||
std::string value;
|
||||
|
||||
std::vector<Slice> multiget_keys = {Slice(key)};
|
||||
std::vector<std::string> values;
|
||||
|
||||
perf_context.Reset();
|
||||
db->Get(read_options, key, &value);
|
||||
ASSERT_OK(db->Get(read_options, key, &value));
|
||||
ASSERT_EQ(expected_value, value);
|
||||
hist_get_snapshot.Add(perf_context.get_snapshot_time);
|
||||
hist_get_memtable.Add(perf_context.get_from_memtable_time);
|
||||
hist_get_files.Add(perf_context.get_from_output_files_time);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "table/internal_iterator.h"
|
||||
|
||||
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 {
|
||||
public:
|
||||
PinnedIteratorsManager() : pinning_enabled(false), pinned_iters_(nullptr) {}
|
||||
~PinnedIteratorsManager() { assert(!pinning_enabled); }
|
||||
|
||||
// Enable Iterators pinning
|
||||
void StartPinning() {
|
||||
if (!pinning_enabled) {
|
||||
pinning_enabled = true;
|
||||
if (!pinned_iters_) {
|
||||
pinned_iters_.reset(new std::vector<InternalIterator*>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is pinning enabled ?
|
||||
bool PinningEnabled() { return pinning_enabled; }
|
||||
|
||||
// Take ownership of iter if pinning is enabled and delete it when
|
||||
// ReleasePinnedIterators() is called
|
||||
void PinIteratorIfNeeded(InternalIterator* iter) {
|
||||
if (!pinning_enabled || !iter) {
|
||||
return;
|
||||
}
|
||||
pinned_iters_->push_back(iter);
|
||||
}
|
||||
|
||||
// Release pinned Iterators
|
||||
void ReleasePinnedIterators() {
|
||||
if (pinning_enabled) {
|
||||
pinning_enabled = false;
|
||||
|
||||
// Remove duplicate pointers
|
||||
std::sort(pinned_iters_->begin(), pinned_iters_->end());
|
||||
std::unique(pinned_iters_->begin(), pinned_iters_->end());
|
||||
|
||||
for (auto& iter : *pinned_iters_) {
|
||||
delete iter;
|
||||
}
|
||||
pinned_iters_->clear();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool pinning_enabled;
|
||||
std::unique_ptr<std::vector<InternalIterator*>> pinned_iters_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -446,6 +446,10 @@ TEST_F(PrefixTest, PrefixValid) {
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(kNotFoundResult, Get(db.get(), read_options, 12346, 8));
|
||||
|
||||
// Verify seeking past the prefix won't return a result.
|
||||
SeekIterator(iter.get(), 12345, 10);
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -297,7 +297,7 @@ class Repairer {
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
|
||||
std::string() /* column_family_name */, {}, kMaxSequenceNumber,
|
||||
kNoCompression, CompressionOptions(), false,
|
||||
nullptr /* internal_stats */);
|
||||
nullptr /* internal_stats */, TableFileCreationReason::kRecovery);
|
||||
}
|
||||
delete mem->Unref();
|
||||
delete cf_mems_default;
|
||||
|
||||
+24
-11
@@ -87,15 +87,16 @@ void TableCache::ReleaseHandle(Cache::Handle* handle) {
|
||||
Status TableCache::GetTableReader(
|
||||
const EnvOptions& env_options,
|
||||
const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,
|
||||
bool sequential_mode, bool record_read_stats, HistogramImpl* file_read_hist,
|
||||
unique_ptr<TableReader>* table_reader, bool skip_filters, int level) {
|
||||
bool sequential_mode, size_t readahead, bool record_read_stats,
|
||||
HistogramImpl* file_read_hist, unique_ptr<TableReader>* table_reader,
|
||||
bool skip_filters, int level) {
|
||||
std::string fname =
|
||||
TableFileName(ioptions_.db_paths, fd.GetNumber(), fd.GetPathId());
|
||||
unique_ptr<RandomAccessFile> file;
|
||||
Status s = ioptions_.env->NewRandomAccessFile(fname, &file, env_options);
|
||||
if (sequential_mode && ioptions_.compaction_readahead_size > 0) {
|
||||
file = NewReadaheadRandomAccessFile(std::move(file),
|
||||
ioptions_.compaction_readahead_size);
|
||||
|
||||
if (readahead > 0) {
|
||||
file = NewReadaheadRandomAccessFile(std::move(file), readahead);
|
||||
}
|
||||
RecordTick(ioptions_.statistics, NO_FILE_OPENS);
|
||||
if (s.ok()) {
|
||||
@@ -143,8 +144,9 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
}
|
||||
unique_ptr<TableReader> table_reader;
|
||||
s = GetTableReader(env_options, internal_comparator, fd,
|
||||
false /* sequential mode */, record_read_stats,
|
||||
file_read_hist, &table_reader, skip_filters, level);
|
||||
false /* sequential mode */, 0 /* readahead */,
|
||||
record_read_stats, file_read_hist, &table_reader,
|
||||
skip_filters, level);
|
||||
if (!s.ok()) {
|
||||
assert(table_reader == nullptr);
|
||||
RecordTick(ioptions_.statistics, NO_FILE_ERRORS);
|
||||
@@ -175,13 +177,24 @@ InternalIterator* TableCache::NewIterator(
|
||||
|
||||
TableReader* table_reader = nullptr;
|
||||
Cache::Handle* handle = nullptr;
|
||||
bool create_new_table_reader =
|
||||
(for_compaction && ioptions_.new_table_reader_for_compaction_inputs);
|
||||
|
||||
size_t readahead = 0;
|
||||
bool create_new_table_reader = false;
|
||||
if (for_compaction) {
|
||||
if (ioptions_.new_table_reader_for_compaction_inputs) {
|
||||
readahead = ioptions_.compaction_readahead_size;
|
||||
create_new_table_reader = true;
|
||||
}
|
||||
} else {
|
||||
readahead = options.readahead_size;
|
||||
create_new_table_reader = readahead > 0;
|
||||
}
|
||||
|
||||
if (create_new_table_reader) {
|
||||
unique_ptr<TableReader> table_reader_unique_ptr;
|
||||
Status s = GetTableReader(
|
||||
env_options, icomparator, fd, /* sequential mode */ true,
|
||||
/* record stats */ false, nullptr, &table_reader_unique_ptr,
|
||||
env_options, icomparator, fd, true /* sequential_mode */, readahead,
|
||||
!for_compaction /* record stats */, nullptr, &table_reader_unique_ptr,
|
||||
false /* skip_filters */, level);
|
||||
if (!s.ok()) {
|
||||
return NewErrorInternalIterator(s, arena);
|
||||
|
||||
+2
-1
@@ -111,7 +111,8 @@ class TableCache {
|
||||
Status GetTableReader(const EnvOptions& env_options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd, bool sequential_mode,
|
||||
bool record_read_stats, HistogramImpl* file_read_hist,
|
||||
size_t readahead, bool record_read_stats,
|
||||
HistogramImpl* file_read_hist,
|
||||
unique_ptr<TableReader>* table_reader,
|
||||
bool skip_filters = false, int level = -1);
|
||||
|
||||
|
||||
@@ -779,6 +779,21 @@ uint64_t VersionStorageInfo::GetEstimatedActiveKeys() const {
|
||||
}
|
||||
}
|
||||
|
||||
double VersionStorageInfo::GetEstimatedCompressionRatioAtLevel(
|
||||
int level) const {
|
||||
assert(level < num_levels_);
|
||||
uint64_t sum_file_size_bytes = 0;
|
||||
uint64_t sum_data_size_bytes = 0;
|
||||
for (auto* file_meta : files_[level]) {
|
||||
sum_file_size_bytes += file_meta->fd.GetFileSize();
|
||||
sum_data_size_bytes += file_meta->raw_key_size + file_meta->raw_value_size;
|
||||
}
|
||||
if (sum_file_size_bytes == 0) {
|
||||
return -1.0;
|
||||
}
|
||||
return static_cast<double>(sum_data_size_bytes) / sum_file_size_bytes;
|
||||
}
|
||||
|
||||
void Version::AddIterators(const ReadOptions& read_options,
|
||||
const EnvOptions& soptions,
|
||||
MergeIteratorBuilder* merge_iter_builder) {
|
||||
|
||||
@@ -301,6 +301,8 @@ class VersionStorageInfo {
|
||||
|
||||
uint64_t GetEstimatedActiveKeys() const;
|
||||
|
||||
double GetEstimatedCompressionRatioAtLevel(int level) const;
|
||||
|
||||
// re-initializes the index that is used to offset into
|
||||
// files_by_compaction_pri_
|
||||
// to find the next compaction candidate file.
|
||||
|
||||
@@ -123,6 +123,7 @@ TEST_F(WriteCallbackTest, WriteWithCallbackTest) {
|
||||
DB* db;
|
||||
DBImpl* db_impl;
|
||||
|
||||
DestroyDB(dbname, options);
|
||||
ASSERT_OK(DB::Open(options, dbname, &db));
|
||||
|
||||
db_impl = dynamic_cast<DBImpl*>(db);
|
||||
@@ -282,6 +283,8 @@ TEST_F(WriteCallbackTest, WriteCallBackTest) {
|
||||
DB* db;
|
||||
DBImpl* db_impl;
|
||||
|
||||
DestroyDB(dbname, options);
|
||||
|
||||
options.create_if_missing = true;
|
||||
Status s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ If you want to compile rocksdb with hdfs support, please set the following
|
||||
environment variables appropriately (also defined in setup.sh for convenience)
|
||||
USE_HDFS=1
|
||||
JAVA_HOME=/usr/local/jdk-7u79-64
|
||||
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/jdk-6u22-64/jre/lib/amd64/server:/usr/local/jdk-6u22-64/jre/lib/amd64/:./snappy/libs
|
||||
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/jdk-7u79-64/jre/lib/amd64/server:/usr/local/jdk-7u79-64/jre/lib/amd64/:./snappy/libs
|
||||
make clean all db_bench
|
||||
|
||||
To run dbbench,
|
||||
|
||||
+14
-1
@@ -121,6 +121,9 @@ extern ROCKSDB_LIBRARY_API rocksdb_backup_engine_t* rocksdb_backup_engine_open(
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_create_new_backup(
|
||||
rocksdb_backup_engine_t* be, rocksdb_t* db, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_backup_engine_purge_old_backups(
|
||||
rocksdb_backup_engine_t* be, uint32_t num_backups_to_keep, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_restore_options_t*
|
||||
rocksdb_restore_options_create();
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_restore_options_destroy(
|
||||
@@ -266,6 +269,11 @@ extern ROCKSDB_LIBRARY_API rocksdb_iterator_t* rocksdb_create_iterator_cf(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_create_iterators(
|
||||
rocksdb_t *db, rocksdb_readoptions_t* opts,
|
||||
rocksdb_column_family_handle_t** column_families,
|
||||
rocksdb_iterator_t** iterators, size_t size, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API const rocksdb_snapshot_t* rocksdb_create_snapshot(
|
||||
rocksdb_t* db);
|
||||
|
||||
@@ -496,6 +504,8 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_filter(
|
||||
rocksdb_options_t*, rocksdb_compactionfilter_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compaction_filter_factory(
|
||||
rocksdb_options_t*, rocksdb_compactionfilterfactory_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_compaction_readahead_size(
|
||||
rocksdb_options_t*, size_t);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_comparator(
|
||||
rocksdb_options_t*, rocksdb_comparator_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_merge_operator(
|
||||
@@ -521,12 +531,14 @@ extern ROCKSDB_LIBRARY_API void rocksdb_options_set_info_log_level(
|
||||
rocksdb_options_t*, int);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_write_buffer_size(
|
||||
rocksdb_options_t*, size_t);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_db_write_buffer_size(
|
||||
rocksdb_options_t*, size_t);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_open_files(
|
||||
rocksdb_options_t*, int);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_max_total_wal_size(
|
||||
rocksdb_options_t* opt, uint64_t n);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_compression_options(
|
||||
rocksdb_options_t*, int, int, int);
|
||||
rocksdb_options_t*, int, int, int, int);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_prefix_extractor(
|
||||
rocksdb_options_t*, rocksdb_slicetransform_t*);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_options_set_num_levels(
|
||||
@@ -835,6 +847,7 @@ extern ROCKSDB_LIBRARY_API void rocksdb_cache_destroy(rocksdb_cache_t* cache);
|
||||
/* Env */
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_env_t* rocksdb_create_default_env();
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_env_t* rocksdb_create_mem_env();
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_env_set_background_threads(
|
||||
rocksdb_env_t* env, int n);
|
||||
extern ROCKSDB_LIBRARY_API void
|
||||
|
||||
@@ -61,7 +61,7 @@ struct CompactionJobStats {
|
||||
uint64_t num_corrupt_keys;
|
||||
|
||||
// Following counters are only populated if
|
||||
// options.compaction_measure_io_stats = true;
|
||||
// options.report_bg_io_stats = true;
|
||||
|
||||
// Time spent on file's Append() call.
|
||||
uint64_t file_write_nanos;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace rocksdb {
|
||||
// base_options, and return the new options as a result.
|
||||
//
|
||||
// If input_strings_escaped is set to true, then each escaped characters
|
||||
// prefixed by '\' in the the values of the opts_map will be further
|
||||
// prefixed by '\' in the values of the opts_map will be further
|
||||
// converted back to the raw string before assigning to the associated
|
||||
// options.
|
||||
Status GetColumnFamilyOptionsFromMap(
|
||||
@@ -30,7 +30,7 @@ Status GetColumnFamilyOptionsFromMap(
|
||||
// base_options, and return the new options as a result.
|
||||
//
|
||||
// If input_strings_escaped is set to true, then each escaped characters
|
||||
// prefixed by '\' in the the values of the opts_map will be further
|
||||
// prefixed by '\' in the values of the opts_map will be further
|
||||
// converted back to the raw string before assigning to the associated
|
||||
// options.
|
||||
Status GetDBOptionsFromMap(
|
||||
|
||||
@@ -338,6 +338,13 @@ class DB {
|
||||
// level number (e.g., "0").
|
||||
static const std::string kNumFilesAtLevelPrefix;
|
||||
|
||||
// "rocksdb.compression-ratio-at-level<N>" - returns string containing the
|
||||
// compression ratio of data at level <N>, where <N> is an ASCII
|
||||
// representation of a level number (e.g., "0"). Here, compression
|
||||
// ratio is defined as uncompressed data size / compressed file size.
|
||||
// Returns "-1.0" if no open files at level <N>.
|
||||
static const std::string kCompressionRatioAtLevelPrefix;
|
||||
|
||||
// "rocksdb.stats" - returns a multi-line string containing the data
|
||||
// described by kCFStats followed by the data described by kDBStats.
|
||||
static const std::string kStats;
|
||||
|
||||
@@ -98,13 +98,11 @@ class Iterator : public Cleanable {
|
||||
|
||||
// Property "rocksdb.iterator.is-key-pinned":
|
||||
// If returning "1", this means that the Slice returned by key() is valid
|
||||
// as long as the iterator is not deleted and ReleasePinnedData() is not
|
||||
// called.
|
||||
// as long as the iterator is not deleted.
|
||||
// It is guaranteed to always return "1" if
|
||||
// - Iterator created with ReadOptions::pin_data = true
|
||||
// - DB tables were created with
|
||||
// BlockBasedTableOptions::use_delta_encoding
|
||||
// set to false.
|
||||
// BlockBasedTableOptions::use_delta_encoding = false.
|
||||
// Property "rocksdb.iterator.super-version-number":
|
||||
// LSM version used by the iterator. The same format as DB Property
|
||||
// kCurrentSuperVersionNumber. See its comment for more information.
|
||||
|
||||
@@ -21,23 +21,36 @@ class DB;
|
||||
class Status;
|
||||
struct CompactionJobStats;
|
||||
|
||||
struct TableFileCreationInfo {
|
||||
TableFileCreationInfo() = default;
|
||||
explicit TableFileCreationInfo(TableProperties&& prop) :
|
||||
table_properties(prop) {}
|
||||
enum class TableFileCreationReason {
|
||||
kFlush,
|
||||
kCompaction,
|
||||
kRecovery,
|
||||
};
|
||||
|
||||
struct TableFileCreationBriefInfo {
|
||||
// the name of the database where the file was created
|
||||
std::string db_name;
|
||||
// the name of the column family where the file was created.
|
||||
std::string cf_name;
|
||||
// the path to the created file.
|
||||
std::string file_path;
|
||||
// the size of the file.
|
||||
uint64_t file_size;
|
||||
// the id of the job (which could be flush or compaction) that
|
||||
// created the file.
|
||||
int job_id;
|
||||
// reason of creating the table.
|
||||
TableFileCreationReason reason;
|
||||
};
|
||||
|
||||
struct TableFileCreationInfo : public TableFileCreationBriefInfo {
|
||||
TableFileCreationInfo() = default;
|
||||
explicit TableFileCreationInfo(TableProperties&& prop)
|
||||
: table_properties(prop) {}
|
||||
// the size of the file.
|
||||
uint64_t file_size;
|
||||
// Detailed properties of the created file.
|
||||
TableProperties table_properties;
|
||||
// The status indicating whether the creation was successful or not.
|
||||
Status status;
|
||||
};
|
||||
|
||||
enum class CompactionReason {
|
||||
@@ -212,11 +225,25 @@ class EventListener {
|
||||
// on file creations and deletions is suggested to implement
|
||||
// OnFlushCompleted and OnCompactionCompleted.
|
||||
//
|
||||
// Historically it will only be called if the file is successfully created.
|
||||
// Now it will also be called on failure case. User can check info.status
|
||||
// to see if it succeeded or not.
|
||||
//
|
||||
// Note that if applications would like to use the passed reference
|
||||
// outside this function call, they should make copies from these
|
||||
// returned value.
|
||||
virtual void OnTableFileCreated(const TableFileCreationInfo& /*info*/) {}
|
||||
|
||||
// A call-back function for RocksDB which will be called before
|
||||
// a SST file is being created. It will follow by OnTableFileCreated after
|
||||
// the creation finishes.
|
||||
//
|
||||
// Note that if applications would like to use the passed reference
|
||||
// outside this function call, they should make copies from these
|
||||
// returned value.
|
||||
virtual void OnTableFileCreationStarted(
|
||||
const TableFileCreationBriefInfo& /*info*/) {}
|
||||
|
||||
virtual ~EventListener() {}
|
||||
};
|
||||
|
||||
|
||||
+52
-13
@@ -61,6 +61,7 @@ enum CompressionType : char {
|
||||
kBZip2Compression = 0x3,
|
||||
kLZ4Compression = 0x4,
|
||||
kLZ4HCCompression = 0x5,
|
||||
kXpressCompression = 0x6,
|
||||
// zstd format is not finalized yet so it's subject to changes.
|
||||
kZSTDNotFinalCompression = 0x40,
|
||||
};
|
||||
@@ -136,9 +137,23 @@ struct CompressionOptions {
|
||||
int window_bits;
|
||||
int level;
|
||||
int strategy;
|
||||
CompressionOptions() : window_bits(-14), level(-1), strategy(0) {}
|
||||
CompressionOptions(int wbits, int _lev, int _strategy)
|
||||
: window_bits(wbits), level(_lev), strategy(_strategy) {}
|
||||
// Maximum size of dictionary used to prime the compression library. Currently
|
||||
// this dictionary will be constructed by sampling the first output file in a
|
||||
// subcompaction when the target level is bottommost. This dictionary will be
|
||||
// loaded into the compression library before compressing/uncompressing each
|
||||
// data block of subsequent files in the subcompaction. Effectively, this
|
||||
// improves compression ratios when there are repetitions across data blocks.
|
||||
// A value of 0 indicates the feature is disabled.
|
||||
// Default: 0.
|
||||
uint32_t max_dict_bytes;
|
||||
|
||||
CompressionOptions()
|
||||
: window_bits(-14), level(-1), strategy(0), max_dict_bytes(0) {}
|
||||
CompressionOptions(int wbits, int _lev, int _strategy, int _max_dict_bytes)
|
||||
: window_bits(wbits),
|
||||
level(_lev),
|
||||
strategy(_strategy),
|
||||
max_dict_bytes(_max_dict_bytes) {}
|
||||
};
|
||||
|
||||
enum UpdateStatus { // Return status For inplace update callback
|
||||
@@ -164,7 +179,7 @@ struct ColumnFamilyOptions {
|
||||
int rocksdb_minor_version = 6);
|
||||
|
||||
// Some functions that make it easier to optimize RocksDB
|
||||
// Use if if your DB is very small (like under 1GB) and you don't want to
|
||||
// Use this if your DB is very small (like under 1GB) and you don't want to
|
||||
// spend lots of memory for memtables.
|
||||
ColumnFamilyOptions* OptimizeForSmallDb();
|
||||
|
||||
@@ -282,7 +297,7 @@ struct ColumnFamilyOptions {
|
||||
|
||||
// The minimum number of write buffers that will be merged together
|
||||
// before writing to storage. If set to 1, then
|
||||
// all write buffers are fushed to L0 as individual files and this increases
|
||||
// all write buffers are flushed to L0 as individual files and this increases
|
||||
// read amplification because a get request has to check in all of these
|
||||
// files. Also, an in-memory merge may result in writing lesser
|
||||
// data to storage if there are duplicate records in each of these
|
||||
@@ -590,7 +605,7 @@ struct ColumnFamilyOptions {
|
||||
|
||||
// If level compaction_style = kCompactionStyleLevel, for each level,
|
||||
// which files are prioritized to be picked to compact.
|
||||
// Default: kCompactionPriByCompensatedSize
|
||||
// Default: kByCompensatedSize
|
||||
CompactionPri compaction_pri;
|
||||
|
||||
// If true, compaction will verify checksum on every read that happens
|
||||
@@ -651,7 +666,7 @@ struct ColumnFamilyOptions {
|
||||
// If you'd like to customize some of these options, you will need to
|
||||
// use NewBlockBasedTableFactory() to construct a new table factory.
|
||||
|
||||
// This option allows user to to collect their own interested statistics of
|
||||
// This option allows user to collect their own interested statistics of
|
||||
// the tables.
|
||||
// Default: empty vector -- no user-defined statistics collection will be
|
||||
// performed.
|
||||
@@ -790,9 +805,9 @@ struct ColumnFamilyOptions {
|
||||
// Default: false
|
||||
bool paranoid_file_checks;
|
||||
|
||||
// Measure IO stats in compactions, if true.
|
||||
// Measure IO stats in compactions and flushes, if true.
|
||||
// Default: false
|
||||
bool compaction_measure_io_stats;
|
||||
bool report_bg_io_stats;
|
||||
|
||||
// Create ColumnFamilyOptions with default values for all fields
|
||||
ColumnFamilyOptions();
|
||||
@@ -809,6 +824,10 @@ struct DBOptions {
|
||||
|
||||
// Some functions that make it easier to optimize RocksDB
|
||||
|
||||
// Use this if your DB is very small (like under 1GB) and you don't want to
|
||||
// spend lots of memory for memtables.
|
||||
DBOptions* OptimizeForSmallDb();
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// By default, RocksDB uses only one background thread for flush and
|
||||
// compaction. Calling this function will set it up such that total of
|
||||
@@ -878,12 +897,12 @@ struct DBOptions {
|
||||
// files opened are always kept open. You can estimate number of files based
|
||||
// on target_file_size_base and target_file_size_multiplier for level-based
|
||||
// compaction. For universal-style compaction, you can usually set it to -1.
|
||||
// Default: 5000 or ulimit value of max open files (whichever is smaller)
|
||||
// Default: -1
|
||||
int max_open_files;
|
||||
|
||||
// If max_open_files is -1, DB will open all files on DB::Open(). You can
|
||||
// use this option to increase the number of threads used to open the files.
|
||||
// Default: 1
|
||||
// Default: 16
|
||||
int max_file_opening_threads;
|
||||
|
||||
// Once write-ahead logs exceed this size, we will start forcing the flush of
|
||||
@@ -964,7 +983,7 @@ struct DBOptions {
|
||||
// Suggested number of concurrent background compaction jobs, submitted to
|
||||
// the default LOW priority thread pool.
|
||||
//
|
||||
// Default: max_background_compactions
|
||||
// Default: 1
|
||||
int base_background_compactions;
|
||||
|
||||
// Maximum number of concurrent background compaction jobs, submitted to
|
||||
@@ -1281,7 +1300,7 @@ struct DBOptions {
|
||||
bool skip_stats_update_on_db_open;
|
||||
|
||||
// Recovery mode to control the consistency while replaying WAL
|
||||
// Default: kTolerateCorruptedTailRecords
|
||||
// Default: kPointInTimeRecovery
|
||||
WALRecoveryMode wal_recovery_mode;
|
||||
|
||||
// A global cache for table-level rows.
|
||||
@@ -1304,6 +1323,11 @@ struct DBOptions {
|
||||
//
|
||||
// DEFAULT: false
|
||||
bool fail_if_options_file_error;
|
||||
|
||||
// If true, then print malloc stats together with rocksdb.stats
|
||||
// when printing to LOG.
|
||||
// DEFAULT: false
|
||||
bool dump_malloc_stats;
|
||||
};
|
||||
|
||||
// Options to control the behavior of a database (passed to DB::Open)
|
||||
@@ -1323,6 +1347,8 @@ struct Options : public DBOptions, public ColumnFamilyOptions {
|
||||
|
||||
void DumpCFOptions(Logger* log) const;
|
||||
|
||||
// Some functions that make it easier to optimize RocksDB
|
||||
|
||||
// Set appropriate parameters for bulk loading.
|
||||
// The reason that this is a function that returns "this" instead of a
|
||||
// constructor is to enable chaining of multiple similar calls in the future.
|
||||
@@ -1332,6 +1358,10 @@ struct Options : public DBOptions, public ColumnFamilyOptions {
|
||||
// It's recommended to manually call CompactRange(NULL, NULL) before reading
|
||||
// from the database, because otherwise the read can be very slow.
|
||||
Options* PrepareForBulkLoad();
|
||||
|
||||
// Use this if your DB is very small (like under 1GB) and you don't want to
|
||||
// spend lots of memory for memtables.
|
||||
Options* OptimizeForSmallDb();
|
||||
};
|
||||
|
||||
//
|
||||
@@ -1428,6 +1458,9 @@ struct ReadOptions {
|
||||
// Enable a total order seek regardless of index format (e.g. hash index)
|
||||
// used in the table. Some table format (e.g. plain table) may not support
|
||||
// this option.
|
||||
// If true when calling Get(), we also skip prefix bloom when reading from
|
||||
// block based table. It provides a way to read exisiting data after
|
||||
// changing implementation of prefix extractor.
|
||||
bool total_order_seek;
|
||||
|
||||
// Enforce that the iterator only iterates over the same prefix as the seek.
|
||||
@@ -1446,6 +1479,12 @@ struct ReadOptions {
|
||||
// Default: false
|
||||
bool pin_data;
|
||||
|
||||
// If non-zero, NewIterator will create a new table reader which
|
||||
// performs reads of the given size. Using a large size (> 2MB) can
|
||||
// improve the performance of forward iteration on spinning disks.
|
||||
// Default: 0
|
||||
size_t readahead_size;
|
||||
|
||||
ReadOptions();
|
||||
ReadOptions(bool cksum, bool cache);
|
||||
};
|
||||
|
||||
@@ -130,7 +130,7 @@ struct BlockBasedTableOptions {
|
||||
int index_block_restart_interval = 1;
|
||||
|
||||
// Use delta encoding to compress keys in blocks.
|
||||
// Iterator::PinData() requires this option to be disabled.
|
||||
// ReadOptions::pin_data requires this option to be disabled.
|
||||
//
|
||||
// Default: true
|
||||
bool use_delta_encoding = true;
|
||||
|
||||
@@ -41,9 +41,13 @@ struct TablePropertiesNames {
|
||||
static const std::string kFilterPolicy;
|
||||
static const std::string kColumnFamilyName;
|
||||
static const std::string kColumnFamilyId;
|
||||
static const std::string kComparator;
|
||||
static const std::string kMergeOperator;
|
||||
static const std::string kPropertyCollectors;
|
||||
};
|
||||
|
||||
extern const std::string kPropertiesBlock;
|
||||
extern const std::string kCompressionDictBlock;
|
||||
|
||||
enum EntryType {
|
||||
kEntryPut,
|
||||
@@ -154,6 +158,18 @@ struct TableProperties {
|
||||
// If no filter policy is used, `filter_policy_name` will be an empty string.
|
||||
std::string filter_policy_name;
|
||||
|
||||
// The name of the comparator used in this table.
|
||||
std::string comparator_name;
|
||||
|
||||
// The name of the merge operator used in this table.
|
||||
// If no merge operator is used, `merge_operator_name` will be "nullptr".
|
||||
std::string merge_operator_name;
|
||||
|
||||
// The names of the property collectors factories used in this table
|
||||
// separated by commas
|
||||
// {collector_name[1]},{collector_name[2]},{collector_name[3]} ..
|
||||
std::string property_collectors_names;
|
||||
|
||||
// user collected properties
|
||||
UserCollectedProperties user_collected_properties;
|
||||
UserCollectedProperties readable_properties;
|
||||
|
||||
@@ -183,6 +183,8 @@ class BackupStatistics {
|
||||
uint32_t number_fail_backup;
|
||||
};
|
||||
|
||||
// A backup engine for accessing information about backups and restoring from
|
||||
// them.
|
||||
class BackupEngineReadOnly {
|
||||
public:
|
||||
virtual ~BackupEngineReadOnly() {}
|
||||
@@ -190,9 +192,12 @@ class BackupEngineReadOnly {
|
||||
static Status Open(Env* db_env, const BackupableDBOptions& options,
|
||||
BackupEngineReadOnly** backup_engine_ptr);
|
||||
|
||||
// Returns info about backups in backup_info
|
||||
// You can GetBackupInfo safely, even with other BackupEngine performing
|
||||
// backups on the same directory
|
||||
virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
|
||||
|
||||
// Returns info about corrupt backups in corrupt_backups
|
||||
virtual void GetCorruptedBackups(
|
||||
std::vector<BackupID>* corrupt_backup_ids) = 0;
|
||||
|
||||
@@ -200,9 +205,12 @@ class BackupEngineReadOnly {
|
||||
// running that might call DeleteBackup() or PurgeOldBackups(). It is caller's
|
||||
// responsibility to synchronize the operation, i.e. don't delete the backup
|
||||
// when you're restoring from it
|
||||
// See also the corresponding doc in BackupEngine
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
|
||||
// See the corresponding doc in BackupEngine
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
@@ -213,70 +221,37 @@ class BackupEngineReadOnly {
|
||||
virtual Status VerifyBackup(BackupID backup_id) = 0;
|
||||
};
|
||||
|
||||
// Please see the documentation in BackupableDB and RestoreBackupableDB
|
||||
// A backup engine for creating new backups.
|
||||
class BackupEngine {
|
||||
public:
|
||||
virtual ~BackupEngine() {}
|
||||
|
||||
// BackupableDBOptions have to be the same as the ones used in previous
|
||||
// BackupEngines for the same backup directory.
|
||||
static Status Open(Env* db_env,
|
||||
const BackupableDBOptions& options,
|
||||
BackupEngine** backup_engine_ptr);
|
||||
|
||||
/// same as CreateNewBackup, but stores extra application metadata
|
||||
// same as CreateNewBackup, but stores extra application metadata
|
||||
virtual Status CreateNewBackupWithMetadata(
|
||||
DB* db, const std::string& app_metadata, bool flush_before_backup = false,
|
||||
std::function<void()> progress_callback = []() {}) = 0;
|
||||
|
||||
// Captures the state of the database in the latest backup
|
||||
// NOT a thread safe call
|
||||
virtual Status CreateNewBackup(DB* db, bool flush_before_backup = false,
|
||||
std::function<void()> progress_callback =
|
||||
[]() {}) {
|
||||
return CreateNewBackupWithMetadata(db, "", flush_before_backup,
|
||||
progress_callback);
|
||||
}
|
||||
virtual Status PurgeOldBackups(uint32_t num_backups_to_keep) = 0;
|
||||
virtual Status DeleteBackup(BackupID backup_id) = 0;
|
||||
virtual void StopBackup() = 0;
|
||||
|
||||
virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
|
||||
virtual void GetCorruptedBackups(
|
||||
std::vector<BackupID>* corrupt_backup_ids) = 0;
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
|
||||
// checks that each file exists and that the size of the file matches our
|
||||
// expectations. it does not check file checksum.
|
||||
// Returns Status::OK() if all checks are good
|
||||
virtual Status VerifyBackup(BackupID backup_id) = 0;
|
||||
|
||||
virtual Status GarbageCollect() = 0;
|
||||
};
|
||||
|
||||
// Stack your DB with BackupableDB to be able to backup the DB
|
||||
class BackupableDB : public StackableDB {
|
||||
public:
|
||||
// BackupableDBOptions have to be the same as the ones used in a previous
|
||||
// incarnation of the DB
|
||||
//
|
||||
// BackupableDB ownes the pointer `DB* db` now. You should not delete it or
|
||||
// use it after the invocation of BackupableDB
|
||||
BackupableDB(DB* db, const BackupableDBOptions& options);
|
||||
virtual ~BackupableDB();
|
||||
|
||||
// Captures the state of the database in the latest backup
|
||||
// NOT a thread safe call
|
||||
Status CreateNewBackup(bool flush_before_backup = false);
|
||||
// Returns info about backups in backup_info
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info);
|
||||
// Returns info about corrupt backups in corrupt_backups
|
||||
void GetCorruptedBackups(std::vector<BackupID>* corrupt_backup_ids);
|
||||
// deletes old backups, keeping latest num_backups_to_keep alive
|
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep);
|
||||
virtual Status PurgeOldBackups(uint32_t num_backups_to_keep) = 0;
|
||||
|
||||
// deletes a specific backup
|
||||
Status DeleteBackup(BackupID backup_id);
|
||||
virtual Status DeleteBackup(BackupID backup_id) = 0;
|
||||
|
||||
// Call this from another thread if you want to stop the backup
|
||||
// that is currently happening. It will return immediatelly, will
|
||||
// not wait for the backup to stop.
|
||||
@@ -284,62 +259,44 @@ class BackupableDB : public StackableDB {
|
||||
// return Status::Incomplete(). It will not clean up after itself, but
|
||||
// the state will remain consistent. The state will be cleaned up
|
||||
// next time you create BackupableDB or RestoreBackupableDB.
|
||||
void StopBackup();
|
||||
|
||||
// Will delete all the files we don't need anymore
|
||||
// It will do the full scan of the files/ directory and delete all the
|
||||
// files that are not referenced.
|
||||
Status GarbageCollect();
|
||||
|
||||
private:
|
||||
BackupEngine* backup_engine_;
|
||||
Status status_;
|
||||
};
|
||||
|
||||
// Use this class to access information about backups and restore from them
|
||||
class RestoreBackupableDB {
|
||||
public:
|
||||
RestoreBackupableDB(Env* db_env, const BackupableDBOptions& options);
|
||||
~RestoreBackupableDB();
|
||||
virtual void StopBackup() = 0;
|
||||
|
||||
// Returns info about backups in backup_info
|
||||
void GetBackupInfo(std::vector<BackupInfo>* backup_info);
|
||||
virtual void GetBackupInfo(std::vector<BackupInfo>* backup_info) = 0;
|
||||
|
||||
// Returns info about corrupt backups in corrupt_backups
|
||||
void GetCorruptedBackups(std::vector<BackupID>* corrupt_backup_ids);
|
||||
virtual void GetCorruptedBackups(
|
||||
std::vector<BackupID>* corrupt_backup_ids) = 0;
|
||||
|
||||
// restore from backup with backup_id
|
||||
// IMPORTANT -- if options_.share_table_files == true and you restore DB
|
||||
// from some backup that is not the latest, and you start creating new
|
||||
// backups from the new DB, they will probably fail
|
||||
// IMPORTANT -- if options_.share_table_files == true,
|
||||
// options_.share_files_with_checksum == false, you restore DB from some
|
||||
// backup that is not the latest, and you start creating new backups from the
|
||||
// new DB, they will probably fail.
|
||||
//
|
||||
// Example: Let's say you have backups 1, 2, 3, 4, 5 and you restore 3.
|
||||
// If you add new data to the DB and try creating a new backup now, the
|
||||
// database will diverge from backups 4 and 5 and the new backup will fail.
|
||||
// If you want to create new backup, you will first have to delete backups 4
|
||||
// and 5.
|
||||
Status RestoreDBFromBackup(BackupID backup_id, const std::string& db_dir,
|
||||
const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options =
|
||||
RestoreOptions());
|
||||
virtual Status RestoreDBFromBackup(
|
||||
BackupID backup_id, const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
|
||||
// restore from the latest backup
|
||||
Status RestoreDBFromLatestBackup(const std::string& db_dir,
|
||||
const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options =
|
||||
RestoreOptions());
|
||||
// deletes old backups, keeping latest num_backups_to_keep alive
|
||||
Status PurgeOldBackups(uint32_t num_backups_to_keep);
|
||||
// deletes a specific backup
|
||||
Status DeleteBackup(BackupID backup_id);
|
||||
virtual Status RestoreDBFromLatestBackup(
|
||||
const std::string& db_dir, const std::string& wal_dir,
|
||||
const RestoreOptions& restore_options = RestoreOptions()) = 0;
|
||||
|
||||
// checks that each file exists and that the size of the file matches our
|
||||
// expectations. it does not check file checksum.
|
||||
// Returns Status::OK() if all checks are good
|
||||
virtual Status VerifyBackup(BackupID backup_id) = 0;
|
||||
|
||||
// Will delete all the files we don't need anymore
|
||||
// It will do the full scan of the files/ directory and delete all the
|
||||
// files that are not referenced.
|
||||
Status GarbageCollect();
|
||||
|
||||
private:
|
||||
BackupEngine* backup_engine_;
|
||||
Status status_;
|
||||
virtual Status GarbageCollect() = 0;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -341,7 +341,7 @@ class Transaction {
|
||||
// committed.
|
||||
//
|
||||
// Note: You should not write or delete anything from the batch directly and
|
||||
// should only use the the functions in the Transaction class to
|
||||
// should only use the functions in the Transaction class to
|
||||
// write to this transaction.
|
||||
virtual WriteBatchWithIndex* GetWriteBatch() = 0;
|
||||
|
||||
|
||||
@@ -54,10 +54,10 @@ struct TransactionDBOptions {
|
||||
// If negative, there is no timeout and will block indefinitely when acquiring
|
||||
// a lock.
|
||||
//
|
||||
// Not using a a timeout can lead to deadlocks. Currently, there
|
||||
// Not using a timeout can lead to deadlocks. Currently, there
|
||||
// is no deadlock-detection to recover from a deadlock. While DB writes
|
||||
// cannot deadlock with other DB writes, they can deadlock with a transaction.
|
||||
// A negative timeout should only be used if all transactions have an small
|
||||
// A negative timeout should only be used if all transactions have a small
|
||||
// expiration set.
|
||||
int64_t default_lock_timeout = 1000; // 1 second
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#define ROCKSDB_MAJOR 4
|
||||
#define ROCKSDB_MINOR 6
|
||||
#define ROCKSDB_MINOR 8
|
||||
#define ROCKSDB_PATCH 0
|
||||
|
||||
// Do not use these. We made the mistake of declaring macros starting with
|
||||
|
||||
@@ -2,7 +2,6 @@ NATIVE_JAVA_CLASSES = org.rocksdb.AbstractCompactionFilter\
|
||||
org.rocksdb.AbstractComparator\
|
||||
org.rocksdb.AbstractSlice\
|
||||
org.rocksdb.BackupEngine\
|
||||
org.rocksdb.BackupableDB\
|
||||
org.rocksdb.BackupableDBOptions\
|
||||
org.rocksdb.BlockBasedTableConfig\
|
||||
org.rocksdb.BloomFilter\
|
||||
@@ -26,7 +25,6 @@ NATIVE_JAVA_CLASSES = org.rocksdb.AbstractCompactionFilter\
|
||||
org.rocksdb.PlainTableConfig\
|
||||
org.rocksdb.ReadOptions\
|
||||
org.rocksdb.RemoveEmptyValueCompactionFilter\
|
||||
org.rocksdb.RestoreBackupableDB\
|
||||
org.rocksdb.RestoreOptions\
|
||||
org.rocksdb.RocksDB\
|
||||
org.rocksdb.RocksEnv\
|
||||
@@ -62,7 +60,6 @@ endif
|
||||
|
||||
JAVA_TESTS = org.rocksdb.BackupableDBOptionsTest\
|
||||
org.rocksdb.BackupEngineTest\
|
||||
org.rocksdb.BackupableDBTest\
|
||||
org.rocksdb.BlockBasedTableConfigTest\
|
||||
org.rocksdb.util.BytewiseComparatorTest\
|
||||
org.rocksdb.CheckPointTest\
|
||||
|
||||
@@ -13,122 +13,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "include/org_rocksdb_BackupableDB.h"
|
||||
#include "include/org_rocksdb_BackupableDBOptions.h"
|
||||
#include "rocksjni/portal.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
* Method: open
|
||||
* Signature: (JJ)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_BackupableDB_open(
|
||||
JNIEnv* env, jclass jcls, jlong jdb_handle, jlong jopt_handle) {
|
||||
auto* db = reinterpret_cast<rocksdb::DB*>(jdb_handle);
|
||||
auto* opt = reinterpret_cast<rocksdb::BackupableDBOptions*>(jopt_handle);
|
||||
auto bdb = new rocksdb::BackupableDB(db, *opt);
|
||||
|
||||
// as BackupableDB extends RocksDB on the java side, we can reuse
|
||||
// the RocksDB portal here.
|
||||
return reinterpret_cast<jlong>(bdb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
* Method: createNewBackup
|
||||
* Signature: (JZ)V
|
||||
*/
|
||||
void Java_org_rocksdb_BackupableDB_createNewBackup(
|
||||
JNIEnv* env, jobject jbdb, jlong jhandle, jboolean jflag) {
|
||||
rocksdb::Status s =
|
||||
reinterpret_cast<rocksdb::BackupableDB*>(jhandle)->CreateNewBackup(jflag);
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
* Method: purgeOldBackups
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_BackupableDB_purgeOldBackups(
|
||||
JNIEnv* env, jobject jbdb, jlong jhandle, jint jnumBackupsToKeep) {
|
||||
rocksdb::Status s =
|
||||
reinterpret_cast<rocksdb::BackupableDB*>(jhandle)->
|
||||
PurgeOldBackups(jnumBackupsToKeep);
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
* Method: deleteBackup0
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_BackupableDB_deleteBackup0(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle, jint jbackup_id) {
|
||||
auto rdb = reinterpret_cast<rocksdb::BackupableDB*>(jhandle);
|
||||
rocksdb::Status s = rdb->DeleteBackup(jbackup_id);
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
* Method: getBackupInfo
|
||||
* Signature: (J)Ljava/util/List;
|
||||
*/
|
||||
jobject Java_org_rocksdb_BackupableDB_getBackupInfo(
|
||||
JNIEnv* env, jobject jbdb, jlong jhandle) {
|
||||
std::vector<rocksdb::BackupInfo> backup_infos;
|
||||
reinterpret_cast<rocksdb::BackupableDB*>(jhandle)->
|
||||
GetBackupInfo(&backup_infos);
|
||||
return rocksdb::BackupInfoListJni::getBackupInfo(env,
|
||||
backup_infos);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
* Method: getCorruptedBackups
|
||||
* Signature: (J)[I;
|
||||
*/
|
||||
jintArray Java_org_rocksdb_BackupableDB_getCorruptedBackups(
|
||||
JNIEnv* env, jobject jbdb, jlong jhandle) {
|
||||
std::vector<rocksdb::BackupID> backup_ids;
|
||||
reinterpret_cast<rocksdb::BackupableDB*>(jhandle)->
|
||||
GetCorruptedBackups(&backup_ids);
|
||||
// store backupids in int array
|
||||
std::vector<jint> int_backup_ids(backup_ids.begin(), backup_ids.end());
|
||||
// Store ints in java array
|
||||
jintArray ret_backup_ids;
|
||||
// Its ok to loose precision here (64->32)
|
||||
jsize ret_backup_ids_size = static_cast<jsize>(backup_ids.size());
|
||||
ret_backup_ids = env->NewIntArray(ret_backup_ids_size);
|
||||
env->SetIntArrayRegion(ret_backup_ids, 0, ret_backup_ids_size,
|
||||
int_backup_ids.data());
|
||||
return ret_backup_ids;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_BackupableDB
|
||||
* Method: garbageCollect
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_BackupableDB_garbageCollect(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle) {
|
||||
auto db = reinterpret_cast<rocksdb::BackupableDB*>(jhandle);
|
||||
rocksdb::Status s = db->GarbageCollect();
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// BackupDBOptions
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "include/org_rocksdb_RestoreOptions.h"
|
||||
#include "include/org_rocksdb_RestoreBackupableDB.h"
|
||||
#include "rocksjni/portal.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
/*
|
||||
@@ -38,159 +37,3 @@ void Java_org_rocksdb_RestoreOptions_disposeInternal(JNIEnv* env, jobject jobj,
|
||||
assert(ropt);
|
||||
delete ropt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: newRestoreBackupableDB
|
||||
* Signature: (J)J
|
||||
*/
|
||||
jlong Java_org_rocksdb_RestoreBackupableDB_newRestoreBackupableDB(JNIEnv* env,
|
||||
jclass jcls, jlong jopt_handle) {
|
||||
auto* opt = reinterpret_cast<rocksdb::BackupableDBOptions*>(jopt_handle);
|
||||
auto rdb = new rocksdb::RestoreBackupableDB(rocksdb::Env::Default(), *opt);
|
||||
return reinterpret_cast<jlong>(rdb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: restoreDBFromBackup0
|
||||
* Signature: (JJLjava/lang/String;Ljava/lang/String;J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_restoreDBFromBackup0(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle, jlong jbackup_id, jstring jdb_dir,
|
||||
jstring jwal_dir, jlong jopt_handle) {
|
||||
auto opt = reinterpret_cast<rocksdb::RestoreOptions*>(jopt_handle);
|
||||
|
||||
const char* cdb_dir = env->GetStringUTFChars(jdb_dir, 0);
|
||||
const char* cwal_dir = env->GetStringUTFChars(jwal_dir, 0);
|
||||
|
||||
auto rdb = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
rocksdb::Status s = rdb->RestoreDBFromBackup(
|
||||
static_cast<rocksdb::BackupID>(jbackup_id), cdb_dir, cwal_dir, *opt);
|
||||
|
||||
env->ReleaseStringUTFChars(jdb_dir, cdb_dir);
|
||||
env->ReleaseStringUTFChars(jwal_dir, cwal_dir);
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: restoreDBFromLatestBackup0
|
||||
* Signature: (JLjava/lang/String;Ljava/lang/String;J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_restoreDBFromLatestBackup0(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle, jstring jdb_dir, jstring jwal_dir,
|
||||
jlong jopt_handle) {
|
||||
auto opt = reinterpret_cast<rocksdb::RestoreOptions*>(jopt_handle);
|
||||
|
||||
const char* cdb_dir = env->GetStringUTFChars(jdb_dir, 0);
|
||||
const char* cwal_dir = env->GetStringUTFChars(jwal_dir, 0);
|
||||
|
||||
auto rdb = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
rocksdb::Status s =
|
||||
rdb->RestoreDBFromLatestBackup(cdb_dir, cwal_dir, *opt);
|
||||
|
||||
env->ReleaseStringUTFChars(jdb_dir, cdb_dir);
|
||||
env->ReleaseStringUTFChars(jwal_dir, cwal_dir);
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: purgeOldBackups0
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_purgeOldBackups0(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle, jint jnum_backups_to_keep) {
|
||||
auto rdb = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
rocksdb::Status s = rdb->PurgeOldBackups(jnum_backups_to_keep);
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: deleteBackup0
|
||||
* Signature: (JI)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_deleteBackup0(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle, jint jbackup_id) {
|
||||
auto rdb = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
rocksdb::Status s = rdb->DeleteBackup(jbackup_id);
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: getBackupInfo
|
||||
* Signature: (J)Ljava/util/List;
|
||||
*/
|
||||
jobject Java_org_rocksdb_RestoreBackupableDB_getBackupInfo(
|
||||
JNIEnv* env, jobject jbdb, jlong jhandle) {
|
||||
std::vector<rocksdb::BackupInfo> backup_infos;
|
||||
reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle)->
|
||||
GetBackupInfo(&backup_infos);
|
||||
return rocksdb::BackupInfoListJni::getBackupInfo(env,
|
||||
backup_infos);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: getCorruptedBackups
|
||||
* Signature: (J)[I;
|
||||
*/
|
||||
jintArray Java_org_rocksdb_RestoreBackupableDB_getCorruptedBackups(
|
||||
JNIEnv* env, jobject jbdb, jlong jhandle) {
|
||||
std::vector<rocksdb::BackupID> backup_ids;
|
||||
reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle)->
|
||||
GetCorruptedBackups(&backup_ids);
|
||||
// store backupids in int array
|
||||
std::vector<jint> int_backup_ids(backup_ids.begin(), backup_ids.end());
|
||||
// Store ints in java array
|
||||
jintArray ret_backup_ids;
|
||||
// Its ok to loose precision here (64->32)
|
||||
jsize ret_backup_ids_size = static_cast<jsize>(backup_ids.size());
|
||||
ret_backup_ids = env->NewIntArray(ret_backup_ids_size);
|
||||
env->SetIntArrayRegion(ret_backup_ids, 0, ret_backup_ids_size,
|
||||
int_backup_ids.data());
|
||||
return ret_backup_ids;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: garbageCollect
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_garbageCollect(
|
||||
JNIEnv* env, jobject jobj, jlong jhandle) {
|
||||
auto db = reinterpret_cast<rocksdb::RestoreBackupableDB*>(
|
||||
jhandle);
|
||||
rocksdb::Status s = db->GarbageCollect();
|
||||
|
||||
if (!s.ok()) {
|
||||
rocksdb::RocksDBExceptionJni::ThrowNew(env, s);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: org_rocksdb_RestoreBackupableDB
|
||||
* Method: disposeInternal
|
||||
* Signature: (J)V
|
||||
*/
|
||||
void Java_org_rocksdb_RestoreBackupableDB_disposeInternal(JNIEnv* env,
|
||||
jobject jobj, jlong jhandle) {
|
||||
auto ropt = reinterpret_cast<rocksdb::RestoreBackupableDB*>(jhandle);
|
||||
assert(ropt);
|
||||
delete ropt;
|
||||
}
|
||||
@@ -1,326 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
package org.rocksdb;
|
||||
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class BackupableDBTest {
|
||||
|
||||
@ClassRule
|
||||
public static final RocksMemoryResource rocksMemoryResource =
|
||||
new RocksMemoryResource();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder dbFolder = new TemporaryFolder();
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder backupFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void backupDb() throws RocksDBException {
|
||||
try (final Options opt = new Options().setCreateIfMissing(true);
|
||||
final BackupableDBOptions bopt = new BackupableDBOptions(
|
||||
backupFolder.getRoot().getAbsolutePath())) {
|
||||
assertThat(bopt.backupDir()).isEqualTo(
|
||||
backupFolder.getRoot().getAbsolutePath());
|
||||
// Open empty database.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Fill database with some test values
|
||||
prepareDatabase(bdb);
|
||||
// Create two backups
|
||||
bdb.createNewBackup(false);
|
||||
bdb.createNewBackup(true);
|
||||
verifyNumberOfValidBackups(bdb, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteBackup() throws RocksDBException {
|
||||
try (final Options opt = new Options().setCreateIfMissing(true);
|
||||
final BackupableDBOptions bopt = new BackupableDBOptions(
|
||||
backupFolder.getRoot().getAbsolutePath())) {
|
||||
assertThat(bopt.backupDir()).isEqualTo(
|
||||
backupFolder.getRoot().getAbsolutePath());
|
||||
// Open empty database.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Fill database with some test values
|
||||
prepareDatabase(bdb);
|
||||
// Create two backups
|
||||
bdb.createNewBackup(false);
|
||||
bdb.createNewBackup(true);
|
||||
List<BackupInfo> backupInfo =
|
||||
verifyNumberOfValidBackups(bdb, 2);
|
||||
// Delete the first backup
|
||||
bdb.deleteBackup(backupInfo.get(0).backupId());
|
||||
final List<BackupInfo> newBackupInfo =
|
||||
verifyNumberOfValidBackups(bdb, 1);
|
||||
// The second backup must remain.
|
||||
assertThat(newBackupInfo.get(0).backupId()).
|
||||
isEqualTo(backupInfo.get(1).backupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteBackupWithRestoreBackupableDB()
|
||||
throws RocksDBException {
|
||||
try (final Options opt = new Options().setCreateIfMissing(true);
|
||||
final BackupableDBOptions bopt = new BackupableDBOptions(
|
||||
backupFolder.getRoot().getAbsolutePath())) {
|
||||
assertThat(bopt.backupDir()).isEqualTo(
|
||||
backupFolder.getRoot().getAbsolutePath());
|
||||
// Open empty database.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Fill database with some test values
|
||||
prepareDatabase(bdb);
|
||||
// Create two backups
|
||||
bdb.createNewBackup(false);
|
||||
bdb.createNewBackup(true);
|
||||
final List<BackupInfo> backupInfo =
|
||||
verifyNumberOfValidBackups(bdb, 2);
|
||||
// init RestoreBackupableDB
|
||||
try (final RestoreBackupableDB rdb = new RestoreBackupableDB(bopt)) {
|
||||
// Delete the first backup
|
||||
rdb.deleteBackup(backupInfo.get(0).backupId());
|
||||
// Fetch backup info using RestoreBackupableDB
|
||||
List<BackupInfo> newBackupInfo = verifyNumberOfValidBackups(rdb, 1);
|
||||
// The second backup must remain.
|
||||
assertThat(newBackupInfo.get(0).backupId()).
|
||||
isEqualTo(backupInfo.get(1).backupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void purgeOldBackups() throws RocksDBException {
|
||||
try (final Options opt = new Options().setCreateIfMissing(true);
|
||||
final BackupableDBOptions bopt = new BackupableDBOptions(
|
||||
backupFolder.getRoot().getAbsolutePath())) {
|
||||
assertThat(bopt.backupDir()).isEqualTo(
|
||||
backupFolder.getRoot().getAbsolutePath());
|
||||
// Open empty database.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Fill database with some test values
|
||||
prepareDatabase(bdb);
|
||||
// Create two backups
|
||||
bdb.createNewBackup(false);
|
||||
bdb.createNewBackup(true);
|
||||
bdb.createNewBackup(true);
|
||||
bdb.createNewBackup(true);
|
||||
final List<BackupInfo> backupInfo =
|
||||
verifyNumberOfValidBackups(bdb, 4);
|
||||
// Delete everything except the latest backup
|
||||
bdb.purgeOldBackups(1);
|
||||
final List<BackupInfo> newBackupInfo =
|
||||
verifyNumberOfValidBackups(bdb, 1);
|
||||
// The latest backup must remain.
|
||||
assertThat(newBackupInfo.get(0).backupId()).
|
||||
isEqualTo(backupInfo.get(3).backupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void purgeOldBackupsWithRestoreBackupableDb()
|
||||
throws RocksDBException {
|
||||
try (final Options opt = new Options().setCreateIfMissing(true);
|
||||
final BackupableDBOptions bopt =
|
||||
new BackupableDBOptions(backupFolder.getRoot().getAbsolutePath())
|
||||
) {
|
||||
assertThat(bopt.backupDir()).isEqualTo(
|
||||
backupFolder.getRoot().getAbsolutePath());
|
||||
// Open empty database.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Fill database with some test values
|
||||
prepareDatabase(bdb);
|
||||
// Create two backups
|
||||
bdb.createNewBackup(false);
|
||||
bdb.createNewBackup(true);
|
||||
bdb.createNewBackup(true);
|
||||
bdb.createNewBackup(true);
|
||||
List<BackupInfo> infos = verifyNumberOfValidBackups(bdb, 4);
|
||||
assertThat(infos.get(1).size()).
|
||||
isEqualTo(infos.get(2).size());
|
||||
assertThat(infos.get(1).numberFiles()).
|
||||
isEqualTo(infos.get(2).numberFiles());
|
||||
long maxTimeBeforePurge = Long.MIN_VALUE;
|
||||
for (BackupInfo backupInfo : infos) {
|
||||
if (maxTimeBeforePurge < backupInfo.timestamp()) {
|
||||
maxTimeBeforePurge = backupInfo.timestamp();
|
||||
}
|
||||
}
|
||||
// init RestoreBackupableDB
|
||||
try (final RestoreBackupableDB rdb = new RestoreBackupableDB(bopt)) {
|
||||
// the same number of backups must
|
||||
// exist using RestoreBackupableDB.
|
||||
verifyNumberOfValidBackups(rdb, 4);
|
||||
rdb.purgeOldBackups(1);
|
||||
infos = verifyNumberOfValidBackups(rdb, 1);
|
||||
assertThat(infos.get(0).timestamp()).
|
||||
isEqualTo(maxTimeBeforePurge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restoreLatestBackup()
|
||||
throws RocksDBException {
|
||||
try (final Options opt = new Options().setCreateIfMissing(true);
|
||||
final BackupableDBOptions bopt =
|
||||
new BackupableDBOptions(
|
||||
backupFolder.getRoot().getAbsolutePath())) {
|
||||
assertThat(bopt.backupDir()).isEqualTo(
|
||||
backupFolder.getRoot().getAbsolutePath());
|
||||
// Open empty database.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Fill database with some test values
|
||||
prepareDatabase(bdb);
|
||||
bdb.createNewBackup(true);
|
||||
verifyNumberOfValidBackups(bdb, 1);
|
||||
bdb.put("key1".getBytes(), "valueV2".getBytes());
|
||||
bdb.put("key2".getBytes(), "valueV2".getBytes());
|
||||
bdb.createNewBackup(true);
|
||||
verifyNumberOfValidBackups(bdb, 2);
|
||||
bdb.put("key1".getBytes(), "valueV3".getBytes());
|
||||
bdb.put("key2".getBytes(), "valueV3".getBytes());
|
||||
assertThat(new String(bdb.get("key1".getBytes()))).endsWith("V3");
|
||||
assertThat(new String(bdb.get("key2".getBytes()))).endsWith("V3");
|
||||
}
|
||||
|
||||
// init RestoreBackupableDB
|
||||
try (final RestoreBackupableDB rdb = new RestoreBackupableDB(bopt)) {
|
||||
verifyNumberOfValidBackups(rdb, 2);
|
||||
// restore db from latest backup
|
||||
rdb.restoreDBFromLatestBackup(dbFolder.getRoot().getAbsolutePath(),
|
||||
dbFolder.getRoot().getAbsolutePath(),
|
||||
new RestoreOptions(false));
|
||||
}
|
||||
|
||||
// Open database again.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Values must have suffix V2 because of restoring latest backup.
|
||||
assertThat(new String(bdb.get("key1".getBytes()))).endsWith("V2");
|
||||
assertThat(new String(bdb.get("key2".getBytes()))).endsWith("V2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restoreFromBackup()
|
||||
throws RocksDBException {
|
||||
try (final Options opt = new Options().setCreateIfMissing(true);
|
||||
final BackupableDBOptions bopt = new BackupableDBOptions(
|
||||
backupFolder.getRoot().getAbsolutePath())) {
|
||||
assertThat(bopt.backupDir()).isEqualTo(
|
||||
backupFolder.getRoot().getAbsolutePath());
|
||||
// Open empty database.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Fill database with some test values
|
||||
prepareDatabase(bdb);
|
||||
bdb.createNewBackup(true);
|
||||
verifyNumberOfValidBackups(bdb, 1);
|
||||
bdb.put("key1".getBytes(), "valueV2".getBytes());
|
||||
bdb.put("key2".getBytes(), "valueV2".getBytes());
|
||||
bdb.createNewBackup(true);
|
||||
verifyNumberOfValidBackups(bdb, 2);
|
||||
bdb.put("key1".getBytes(), "valueV3".getBytes());
|
||||
bdb.put("key2".getBytes(), "valueV3".getBytes());
|
||||
assertThat(new String(bdb.get("key1".getBytes()))).endsWith("V3");
|
||||
assertThat(new String(bdb.get("key2".getBytes()))).endsWith("V3");
|
||||
}
|
||||
|
||||
// init RestoreBackupableDB
|
||||
try (final RestoreBackupableDB rdb = new RestoreBackupableDB(bopt)) {
|
||||
final List<BackupInfo> backupInfo = verifyNumberOfValidBackups(rdb, 2);
|
||||
// restore db from first backup
|
||||
rdb.restoreDBFromBackup(backupInfo.get(0).backupId(),
|
||||
dbFolder.getRoot().getAbsolutePath(),
|
||||
dbFolder.getRoot().getAbsolutePath(),
|
||||
new RestoreOptions(false));
|
||||
}
|
||||
|
||||
// Open database again.
|
||||
try (final BackupableDB bdb = BackupableDB.open(opt, bopt,
|
||||
dbFolder.getRoot().getAbsolutePath())) {
|
||||
// Values must have suffix V2 because of restoring latest backup.
|
||||
assertThat(new String(bdb.get("key1".getBytes()))).endsWith("V1");
|
||||
assertThat(new String(bdb.get("key2".getBytes()))).endsWith("V1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify backups.
|
||||
*
|
||||
* @param bdb {@link BackupableDB} instance.
|
||||
* @param expectedNumberOfBackups numerical value
|
||||
* @throws RocksDBException thrown if an error occurs within the native
|
||||
* part of the library.
|
||||
*/
|
||||
private List<BackupInfo> verifyNumberOfValidBackups(final BackupableDB bdb,
|
||||
final int expectedNumberOfBackups) throws RocksDBException {
|
||||
// Verify that backups exist
|
||||
assertThat(bdb.getCorruptedBackups().length).
|
||||
isEqualTo(0);
|
||||
bdb.garbageCollect();
|
||||
List<BackupInfo> backupInfo = bdb.getBackupInfos();
|
||||
assertThat(backupInfo.size()).
|
||||
isEqualTo(expectedNumberOfBackups);
|
||||
return backupInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify backups.
|
||||
*
|
||||
* @param rdb {@link RestoreBackupableDB} instance.
|
||||
* @param expectedNumberOfBackups numerical value
|
||||
* @throws RocksDBException thrown if an error occurs within the native
|
||||
* part of the library.
|
||||
*/
|
||||
private List<BackupInfo> verifyNumberOfValidBackups(
|
||||
final RestoreBackupableDB rdb, final int expectedNumberOfBackups)
|
||||
throws RocksDBException {
|
||||
// Verify that backups exist
|
||||
assertThat(rdb.getCorruptedBackups().length).
|
||||
isEqualTo(0);
|
||||
rdb.garbageCollect();
|
||||
List<BackupInfo> backupInfo = rdb.getBackupInfos();
|
||||
assertThat(backupInfo.size()).
|
||||
isEqualTo(expectedNumberOfBackups);
|
||||
return backupInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill database with some test values.
|
||||
*
|
||||
* @param db {@link RocksDB} instance.
|
||||
* @throws RocksDBException thrown if an error occurs within the native
|
||||
* part of the library.
|
||||
*/
|
||||
private void prepareDatabase(final RocksDB db)
|
||||
throws RocksDBException {
|
||||
db.put("key1".getBytes(), "valueV1".getBytes());
|
||||
db.put("key2".getBytes(), "valueV1".getBytes());
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -35,7 +35,7 @@ static int PthreadCall(const char* label, int result) {
|
||||
}
|
||||
|
||||
Mutex::Mutex(bool adaptive) {
|
||||
#ifdef OS_LINUX
|
||||
#ifdef ROCKSDB_PTHREAD_ADAPTIVE_MUTEX
|
||||
if (!adaptive) {
|
||||
PthreadCall("init mutex", pthread_mutex_init(&mu_, nullptr));
|
||||
} else {
|
||||
@@ -48,9 +48,9 @@ Mutex::Mutex(bool adaptive) {
|
||||
PthreadCall("destroy mutex attr",
|
||||
pthread_mutexattr_destroy(&mutex_attr));
|
||||
}
|
||||
#else // ignore adaptive for non-linux platform
|
||||
#else
|
||||
PthreadCall("init mutex", pthread_mutex_init(&mu_, nullptr));
|
||||
#endif // OS_LINUX
|
||||
#endif // ROCKSDB_PTHREAD_ADAPTIVE_MUTEX
|
||||
}
|
||||
|
||||
Mutex::~Mutex() { PthreadCall("destroy mutex", pthread_mutex_destroy(&mu_)); }
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
//
|
||||
#include "port/stack_trace.h"
|
||||
|
||||
#if defined(ROCKSDB_LITE) || !(defined(OS_LINUX) || defined(OS_MACOSX)) || \
|
||||
#if defined(ROCKSDB_LITE) || !(defined(ROCKSDB_BACKTRACE) || defined(OS_MACOSX)) || \
|
||||
defined(CYGWIN)
|
||||
|
||||
// noop
|
||||
|
||||
+88
-104
@@ -262,8 +262,11 @@ class WinMmapFile : public WritableFile {
|
||||
// page size or SSD page size
|
||||
const size_t
|
||||
allocation_granularity_; // View must start at such a granularity
|
||||
size_t mapping_size_; // We want file mapping to be of a specific size
|
||||
// because then the file is expandable
|
||||
|
||||
size_t reserved_size_; // Preallocated size
|
||||
|
||||
size_t mapping_size_; // The max size of the mapping object
|
||||
// we want to guess the final file size to minimize the remapping
|
||||
size_t view_size_; // How much memory to map into a view at a time
|
||||
|
||||
char* mapped_begin_; // Must begin at the file offset that is aligned with
|
||||
@@ -283,15 +286,6 @@ class WinMmapFile : public WritableFile {
|
||||
return ftruncate(filename_, hFile_, toSize);
|
||||
}
|
||||
|
||||
// Can only truncate or reserve to a sector size aligned if
|
||||
// used on files that are opened with Unbuffered I/O
|
||||
// Normally it does not present a problem since in memory mapped files
|
||||
// we do not disable buffering
|
||||
Status ReserveFileSpace(uint64_t toSize) {
|
||||
IOSTATS_TIMER_GUARD(allocate_nanos);
|
||||
return fallocate(filename_, hFile_, toSize);
|
||||
}
|
||||
|
||||
Status UnmapCurrentRegion() {
|
||||
Status status;
|
||||
|
||||
@@ -301,82 +295,57 @@ class WinMmapFile : public WritableFile {
|
||||
"Failed to unmap file view: " + filename_, GetLastError());
|
||||
}
|
||||
|
||||
// UnmapView automatically sends data to disk but not the metadata
|
||||
// which is good and provides some equivalent of fdatasync() on Linux
|
||||
// therefore, we donot need separate flag for metadata
|
||||
pending_sync_ = false;
|
||||
mapped_begin_ = nullptr;
|
||||
mapped_end_ = nullptr;
|
||||
dst_ = nullptr;
|
||||
last_sync_ = nullptr;
|
||||
|
||||
// Move on to the next portion of the file
|
||||
file_offset_ += view_size_;
|
||||
|
||||
// Increase the amount we map the next time, but capped at 1MB
|
||||
view_size_ *= 2;
|
||||
view_size_ = std::min(view_size_, c_OneMB);
|
||||
// UnmapView automatically sends data to disk but not the metadata
|
||||
// which is good and provides some equivalent of fdatasync() on Linux
|
||||
// therefore, we donot need separate flag for metadata
|
||||
mapped_begin_ = nullptr;
|
||||
mapped_end_ = nullptr;
|
||||
dst_ = nullptr;
|
||||
|
||||
last_sync_ = nullptr;
|
||||
pending_sync_ = false;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status MapNewRegion() {
|
||||
|
||||
Status status;
|
||||
|
||||
assert(mapped_begin_ == nullptr);
|
||||
|
||||
size_t minMappingSize = file_offset_ + view_size_;
|
||||
size_t minDiskSize = file_offset_ + view_size_;
|
||||
|
||||
// Check if we need to create a new mapping since we want to write beyond
|
||||
// the current one
|
||||
// If the mapping view is now too short
|
||||
// CreateFileMapping will extend the size of the file automatically if the
|
||||
// mapping size is greater than
|
||||
// the current length of the file, which reserves the space and makes
|
||||
// writing faster, except, windows can not map an empty file.
|
||||
// Thus the first time around we must actually extend the file ourselves
|
||||
if (hMap_ == NULL || minMappingSize > mapping_size_) {
|
||||
if (NULL == hMap_) {
|
||||
// Creating mapping for the first time so reserve the space on disk
|
||||
status = ReserveFileSpace(minMappingSize);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
if (minDiskSize > reserved_size_) {
|
||||
status = Allocate(file_offset_, view_size_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
if (hMap_) {
|
||||
// Need to remap
|
||||
if (hMap_ == NULL || reserved_size_ > mapping_size_) {
|
||||
|
||||
if (hMap_ != NULL) {
|
||||
// Unmap the previous one
|
||||
BOOL ret = ::CloseHandle(hMap_);
|
||||
assert(ret);
|
||||
hMap_ = NULL;
|
||||
}
|
||||
|
||||
// Calculate the new mapping size which will hopefully reserve space for
|
||||
// several consecutive sliding views
|
||||
// Query preallocation block size if set
|
||||
size_t preallocationBlockSize = 0;
|
||||
size_t lastAllocatedBlockSize = 0; // Not used
|
||||
GetPreallocationStatus(&preallocationBlockSize, &lastAllocatedBlockSize);
|
||||
|
||||
if (preallocationBlockSize) {
|
||||
preallocationBlockSize =
|
||||
Roundup(preallocationBlockSize, allocation_granularity_);
|
||||
} else {
|
||||
preallocationBlockSize = 2 * view_size_;
|
||||
}
|
||||
|
||||
mapping_size_ += preallocationBlockSize;
|
||||
|
||||
ULARGE_INTEGER mappingSize;
|
||||
mappingSize.QuadPart = mapping_size_;
|
||||
mappingSize.QuadPart = reserved_size_;
|
||||
|
||||
hMap_ = CreateFileMappingA(
|
||||
hFile_,
|
||||
NULL, // Security attributes
|
||||
PAGE_READWRITE, // There is not a write only mode for mapping
|
||||
mappingSize.HighPart, // Enable mapping the whole file but the actual
|
||||
// amount mapped is determined by MapViewOfFile
|
||||
// amount mapped is determined by MapViewOfFile
|
||||
mappingSize.LowPart,
|
||||
NULL); // Mapping name
|
||||
|
||||
@@ -385,6 +354,8 @@ class WinMmapFile : public WritableFile {
|
||||
"WindowsMmapFile failed to create file mapping for: " + filename_,
|
||||
GetLastError());
|
||||
}
|
||||
|
||||
mapping_size_ = reserved_size_;
|
||||
}
|
||||
|
||||
ULARGE_INTEGER offset;
|
||||
@@ -416,6 +387,7 @@ class WinMmapFile : public WritableFile {
|
||||
hMap_(NULL),
|
||||
page_size_(page_size),
|
||||
allocation_granularity_(allocation_granularity),
|
||||
reserved_size_(0),
|
||||
mapping_size_(0),
|
||||
view_size_(0),
|
||||
mapped_begin_(nullptr),
|
||||
@@ -435,25 +407,10 @@ class WinMmapFile : public WritableFile {
|
||||
// Only for memory mapped writes
|
||||
assert(options.use_mmap_writes);
|
||||
|
||||
// Make sure buffering is not disabled. It is ignored for mapping
|
||||
// purposes but also imposes restriction on moving file position
|
||||
// it is not a problem so much with reserving space since it is probably a
|
||||
// factor
|
||||
// of allocation_granularity but we also want to truncate the file in
|
||||
// Close() at
|
||||
// arbitrary position so we do not have to feel this with zeros.
|
||||
assert(options.use_os_buffer);
|
||||
|
||||
// View size must be both the multiple of allocation_granularity AND the
|
||||
// page size
|
||||
if ((allocation_granularity_ % page_size_) == 0) {
|
||||
view_size_ = 2 * allocation_granularity;
|
||||
} else if ((page_size_ % allocation_granularity_) == 0) {
|
||||
view_size_ = 2 * page_size_;
|
||||
} else {
|
||||
// we can multiply them together
|
||||
assert(false);
|
||||
}
|
||||
// page size and the granularity is usually a multiple of a page size.
|
||||
const size_t viewSize = 32 * 1024; // 32Kb similar to the Windows File Cache in buffered mode
|
||||
view_size_ = Roundup(viewSize, allocation_granularity_);
|
||||
}
|
||||
|
||||
~WinMmapFile() {
|
||||
@@ -479,14 +436,20 @@ class WinMmapFile : public WritableFile {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
size_t n = std::min(left, avail);
|
||||
memcpy(dst_, src, n);
|
||||
dst_ += n;
|
||||
src += n;
|
||||
left -= n;
|
||||
pending_sync_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
size_t n = std::min(left, avail);
|
||||
memcpy(dst_, src, n);
|
||||
dst_ += n;
|
||||
src += n;
|
||||
left -= n;
|
||||
pending_sync_ = true;
|
||||
// Now make sure that the last partial page is padded with zeros if needed
|
||||
size_t bytesToPad = Roundup(size_t(dst_), page_size_) - size_t(dst_);
|
||||
if (bytesToPad > 0) {
|
||||
memset(dst_, 0, bytesToPad);
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
@@ -508,7 +471,13 @@ class WinMmapFile : public WritableFile {
|
||||
// which we use does not write zeros and it is good.
|
||||
uint64_t targetSize = GetFileSize();
|
||||
|
||||
s = UnmapCurrentRegion();
|
||||
if (mapped_begin_ != nullptr) {
|
||||
// Sync before unmapping to make sure everything
|
||||
// is on disk and there is not a lazy writing
|
||||
// so we are deterministic with the tests
|
||||
Sync();
|
||||
s = UnmapCurrentRegion();
|
||||
}
|
||||
|
||||
if (NULL != hMap_) {
|
||||
BOOL ret = ::CloseHandle(hMap_);
|
||||
@@ -521,15 +490,18 @@ class WinMmapFile : public WritableFile {
|
||||
hMap_ = NULL;
|
||||
}
|
||||
|
||||
TruncateFile(targetSize);
|
||||
if (hFile_ != NULL) {
|
||||
|
||||
BOOL ret = ::CloseHandle(hFile_);
|
||||
hFile_ = NULL;
|
||||
TruncateFile(targetSize);
|
||||
|
||||
if (!ret && s.ok()) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError(
|
||||
"Failed to close file map handle: " + filename_, lastError);
|
||||
BOOL ret = ::CloseHandle(hFile_);
|
||||
hFile_ = NULL;
|
||||
|
||||
if (!ret && s.ok()) {
|
||||
auto lastError = GetLastError();
|
||||
s = IOErrorFromWindowsError(
|
||||
"Failed to close file map handle: " + filename_, lastError);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -542,7 +514,7 @@ class WinMmapFile : public WritableFile {
|
||||
Status s;
|
||||
|
||||
// Some writes occurred since last sync
|
||||
if (pending_sync_) {
|
||||
if (dst_ > last_sync_) {
|
||||
assert(mapped_begin_);
|
||||
assert(dst_);
|
||||
assert(dst_ > mapped_begin_);
|
||||
@@ -552,16 +524,15 @@ class WinMmapFile : public WritableFile {
|
||||
TruncateToPageBoundary(page_size_, last_sync_ - mapped_begin_);
|
||||
size_t page_end =
|
||||
TruncateToPageBoundary(page_size_, dst_ - mapped_begin_ - 1);
|
||||
last_sync_ = dst_;
|
||||
|
||||
// Flush only the amount of that is a multiple of pages
|
||||
if (!::FlushViewOfFile(mapped_begin_ + page_begin,
|
||||
(page_end - page_begin) + page_size_)) {
|
||||
(page_end - page_begin) + page_size_)) {
|
||||
s = IOErrorFromWindowsError("Failed to FlushViewOfFile: " + filename_,
|
||||
GetLastError());
|
||||
} else {
|
||||
last_sync_ = dst_;
|
||||
}
|
||||
|
||||
pending_sync_ = false;
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -571,19 +542,15 @@ class WinMmapFile : public WritableFile {
|
||||
* Flush data as well as metadata to stable storage.
|
||||
*/
|
||||
virtual Status Fsync() override {
|
||||
Status s;
|
||||
|
||||
// Flush metadata if pending
|
||||
const bool pending = pending_sync_;
|
||||
|
||||
s = Sync();
|
||||
Status s = Sync();
|
||||
|
||||
// Flush metadata
|
||||
if (s.ok() && pending) {
|
||||
if (s.ok() && pending_sync_) {
|
||||
if (!::FlushFileBuffers(hFile_)) {
|
||||
s = IOErrorFromWindowsError("Failed to FlushFileBuffers: " + filename_,
|
||||
GetLastError());
|
||||
}
|
||||
pending_sync_ = false;
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -604,7 +571,24 @@ class WinMmapFile : public WritableFile {
|
||||
}
|
||||
|
||||
virtual Status Allocate(uint64_t offset, uint64_t len) override {
|
||||
return Status::OK();
|
||||
Status status;
|
||||
TEST_KILL_RANDOM("WinMmapFile::Allocate", rocksdb_kill_odds);
|
||||
|
||||
// Make sure that we reserve an aligned amount of space
|
||||
// since the reservation block size is driven outside so we want
|
||||
// to check if we are ok with reservation here
|
||||
size_t spaceToReserve = Roundup(offset + len, view_size_);
|
||||
// Nothing to do
|
||||
if (spaceToReserve <= reserved_size_) {
|
||||
return status;
|
||||
}
|
||||
|
||||
IOSTATS_TIMER_GUARD(allocate_nanos);
|
||||
status = fallocate(filename_, hFile_, spaceToReserve);
|
||||
if (status.ok()) {
|
||||
reserved_size_ = spaceToReserve;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1928,7 +1912,7 @@ class WinEnv : public Env {
|
||||
// generation time.
|
||||
std::thread& terminating_thread = bgthreads_.back();
|
||||
auto tid = terminating_thread.get_id();
|
||||
// Ensure that that this thread is ours
|
||||
// Ensure that this thread is ours
|
||||
assert(tid == std::this_thread::get_id());
|
||||
terminating_thread.detach();
|
||||
bgthreads_.pop_back();
|
||||
|
||||
@@ -56,23 +56,6 @@ typedef SSIZE_T ssize_t;
|
||||
|
||||
#define __attribute__(A)
|
||||
|
||||
#ifdef ZLIB
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
#ifdef BZIP2
|
||||
#include <bzlib.h>
|
||||
#endif
|
||||
|
||||
#if defined(LZ4)
|
||||
#include <lz4.h>
|
||||
#include <lz4hc.h>
|
||||
#endif
|
||||
|
||||
#ifdef SNAPPY
|
||||
#include <snappy.h>
|
||||
#endif
|
||||
|
||||
// Thread local storage on Linux
|
||||
// There is thread_local in C++11
|
||||
#ifndef __thread
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "port/win/xpress_win.h"
|
||||
#include <Windows.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef XPRESS
|
||||
|
||||
#ifdef JEMALLOC
|
||||
#include <jemalloc/jemalloc.h>
|
||||
#endif
|
||||
|
||||
// Put this under ifdef so windows systems w/o this
|
||||
// can still build
|
||||
#include <compressapi.h>
|
||||
|
||||
namespace rocksdb {
|
||||
namespace port {
|
||||
namespace xpress {
|
||||
|
||||
// Helpers
|
||||
namespace {
|
||||
|
||||
auto CloseCompressorFun = [](void* h) {
|
||||
if (NULL != h) {
|
||||
::CloseCompressor(reinterpret_cast<COMPRESSOR_HANDLE>(h));
|
||||
}
|
||||
};
|
||||
|
||||
auto CloseDecompressorFun = [](void* h) {
|
||||
if (NULL != h) {
|
||||
::CloseDecompressor(reinterpret_cast<DECOMPRESSOR_HANDLE>(h));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#ifdef JEMALLOC
|
||||
// Make sure compressors use our jemalloc if redirected
|
||||
PVOID CompressorAlloc(PVOID, SIZE_T size) {
|
||||
return je_malloc(size);
|
||||
}
|
||||
|
||||
VOID CompressorFree(PVOID, PVOID p) {
|
||||
if (p != NULL) {
|
||||
je_free(p);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
bool Compress(const char* input, size_t length, std::string* output) {
|
||||
|
||||
assert(input != nullptr);
|
||||
assert(output != nullptr);
|
||||
|
||||
if (length == 0) {
|
||||
output->clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
COMPRESS_ALLOCATION_ROUTINES* allocRoutinesPtr = nullptr;
|
||||
|
||||
#ifdef JEMALLOC
|
||||
COMPRESS_ALLOCATION_ROUTINES allocationRoutines;
|
||||
|
||||
// Init. allocation routines
|
||||
allocationRoutines.Allocate = CompressorAlloc;
|
||||
allocationRoutines.Free = CompressorFree;
|
||||
allocationRoutines.UserContext = NULL;
|
||||
|
||||
allocRoutinesPtr = &allocationRoutines;
|
||||
#endif
|
||||
|
||||
COMPRESSOR_HANDLE compressor = NULL;
|
||||
|
||||
BOOL success = CreateCompressor(
|
||||
COMPRESS_ALGORITHM_XPRESS, // Compression Algorithm
|
||||
allocRoutinesPtr, // Optional allocation routine
|
||||
&compressor); // Handle
|
||||
|
||||
if (!success) {
|
||||
#ifdef _DEBUG
|
||||
std::cerr << "XPRESS: Failed to create Compressor LastError: " <<
|
||||
GetLastError() << std::endl;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<void, decltype(CloseCompressorFun)>
|
||||
compressorGuard(compressor, CloseCompressorFun);
|
||||
|
||||
SIZE_T compressedBufferSize = 0;
|
||||
|
||||
// Query compressed buffer size.
|
||||
success = ::Compress(
|
||||
compressor, // Compressor Handle
|
||||
const_cast<char*>(input), // Input buffer
|
||||
length, // Uncompressed data size
|
||||
NULL, // Compressed Buffer
|
||||
0, // Compressed Buffer size
|
||||
&compressedBufferSize); // Compressed Data size
|
||||
|
||||
if (!success) {
|
||||
|
||||
auto lastError = GetLastError();
|
||||
|
||||
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
|
||||
#ifdef _DEBUG
|
||||
std::cerr <<
|
||||
"XPRESS: Failed to estimate compressed buffer size LastError " <<
|
||||
lastError << std::endl;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
assert(compressedBufferSize > 0);
|
||||
|
||||
std::string result;
|
||||
result.resize(compressedBufferSize);
|
||||
|
||||
SIZE_T compressedDataSize = 0;
|
||||
|
||||
// Compress
|
||||
success = ::Compress(
|
||||
compressor, // Compressor Handle
|
||||
const_cast<char*>(input), // Input buffer
|
||||
length, // Uncompressed data size
|
||||
&result[0], // Compressed Buffer
|
||||
compressedBufferSize, // Compressed Buffer size
|
||||
&compressedDataSize); // Compressed Data size
|
||||
|
||||
if (!success) {
|
||||
#ifdef _DEBUG
|
||||
std::cerr << "XPRESS: Failed to compress LastError " <<
|
||||
GetLastError() << std::endl;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
result.resize(compressedDataSize);
|
||||
output->swap(result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char* Decompress(const char* input_data, size_t input_length,
|
||||
int* decompress_size) {
|
||||
|
||||
assert(input_data != nullptr);
|
||||
assert(decompress_size != nullptr);
|
||||
|
||||
if (input_length == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
COMPRESS_ALLOCATION_ROUTINES* allocRoutinesPtr = nullptr;
|
||||
|
||||
#ifdef JEMALLOC
|
||||
COMPRESS_ALLOCATION_ROUTINES allocationRoutines;
|
||||
|
||||
// Init. allocation routines
|
||||
allocationRoutines.Allocate = CompressorAlloc;
|
||||
allocationRoutines.Free = CompressorFree;
|
||||
allocationRoutines.UserContext = NULL;
|
||||
allocRoutinesPtr = &allocationRoutines;
|
||||
#endif
|
||||
|
||||
DECOMPRESSOR_HANDLE decompressor = NULL;
|
||||
|
||||
BOOL success = CreateDecompressor(
|
||||
COMPRESS_ALGORITHM_XPRESS, // Compression Algorithm
|
||||
allocRoutinesPtr, // Optional allocation routine
|
||||
&decompressor); // Handle
|
||||
|
||||
|
||||
if (!success) {
|
||||
#ifdef _DEBUG
|
||||
std::cerr <<
|
||||
"XPRESS: Failed to create Decompressor LastError " <<
|
||||
GetLastError() << std::endl;
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<void, decltype(CloseDecompressorFun)>
|
||||
compressorGuard(decompressor, CloseDecompressorFun);
|
||||
|
||||
SIZE_T decompressedBufferSize = 0;
|
||||
|
||||
success = ::Decompress(
|
||||
decompressor, // Compressor Handle
|
||||
const_cast<char*>(input_data), // Compressed data
|
||||
input_length, // Compressed data size
|
||||
NULL, // Buffer set to NULL
|
||||
0, // Buffer size set to 0
|
||||
&decompressedBufferSize); // Decompressed Data size
|
||||
|
||||
if (!success) {
|
||||
|
||||
auto lastError = GetLastError();
|
||||
|
||||
if (lastError != ERROR_INSUFFICIENT_BUFFER) {
|
||||
#ifdef _DEBUG
|
||||
std::cerr <<
|
||||
"XPRESS: Failed to estimate decompressed buffer size LastError " <<
|
||||
lastError << std::endl;
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
assert(decompressedBufferSize > 0);
|
||||
|
||||
// On Windows we are limited to a 32-bit int for the
|
||||
// output data size argument
|
||||
// so we hopefully never get here
|
||||
if (decompressedBufferSize > std::numeric_limits<int>::max()) {
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The callers are deallocating using delete[]
|
||||
// thus we must allocate with new[]
|
||||
std::unique_ptr<char[]> outputBuffer(new char[decompressedBufferSize]);
|
||||
|
||||
SIZE_T decompressedDataSize = 0;
|
||||
|
||||
success = ::Decompress(
|
||||
decompressor,
|
||||
const_cast<char*>(input_data),
|
||||
input_length,
|
||||
outputBuffer.get(),
|
||||
decompressedBufferSize,
|
||||
&decompressedDataSize);
|
||||
|
||||
if (!success) {
|
||||
#ifdef _DEBUG
|
||||
std::cerr <<
|
||||
"XPRESS: Failed to decompress LastError " <<
|
||||
GetLastError() << std::endl;
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*decompress_size = static_cast<int>(decompressedDataSize);
|
||||
|
||||
// Return the raw buffer to the caller supporting the tradition
|
||||
return outputBuffer.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace rocksdb {
|
||||
namespace port {
|
||||
namespace xpress {
|
||||
|
||||
bool Compress(const char* input, size_t length, std::string* output);
|
||||
|
||||
char* Decompress(const char* input_data, size_t input_length,
|
||||
int* decompress_size);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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.
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
// Xpress on Windows is implemeted using Win API
|
||||
#if defined(ROCKSDB_PLATFORM_POSIX)
|
||||
#error "Xpress compression not implemented"
|
||||
#elif defined(OS_WIN)
|
||||
#include "port/win/xpress_win.h"
|
||||
#endif
|
||||
@@ -198,13 +198,17 @@ TEST_BENCH_SOURCES = \
|
||||
db/dbformat_test.cc \
|
||||
db/db_iter_test.cc \
|
||||
db/db_test.cc \
|
||||
db/db_block_cache_test.cc \
|
||||
db/db_bloom_filter_test.cc \
|
||||
db/db_compaction_filter_test.cc \
|
||||
db/db_compaction_test.cc \
|
||||
db/db_dynamic_level_test.cc \
|
||||
db/db_inplace_update_test.cc \
|
||||
db/db_iterator_test.cc \
|
||||
db/db_log_iter_test.cc \
|
||||
db/db_universal_compaction_test.cc \
|
||||
db/db_sst_test.cc \
|
||||
db/db_tailing_iter_test.cc \
|
||||
db/db_universal_compaction_test.cc \
|
||||
db/db_wal_test.cc \
|
||||
db/db_table_properties_test.cc \
|
||||
db/deletefile_test.cc \
|
||||
|
||||
@@ -78,27 +78,30 @@ std::string AdaptiveTableFactory::GetPrintableTableOptions() const {
|
||||
const int kBufferSize = 200;
|
||||
char buffer[kBufferSize];
|
||||
|
||||
if (!table_factory_to_write_) {
|
||||
if (table_factory_to_write_) {
|
||||
snprintf(buffer, kBufferSize, " write factory (%s) options:\n%s\n",
|
||||
table_factory_to_write_->Name(),
|
||||
(table_factory_to_write_->Name() ? table_factory_to_write_->Name()
|
||||
: ""),
|
||||
table_factory_to_write_->GetPrintableTableOptions().c_str());
|
||||
ret.append(buffer);
|
||||
}
|
||||
if (!plain_table_factory_) {
|
||||
if (plain_table_factory_) {
|
||||
snprintf(buffer, kBufferSize, " %s options:\n%s\n",
|
||||
plain_table_factory_->Name(),
|
||||
plain_table_factory_->Name() ? plain_table_factory_->Name() : "",
|
||||
plain_table_factory_->GetPrintableTableOptions().c_str());
|
||||
ret.append(buffer);
|
||||
}
|
||||
if (!block_based_table_factory_) {
|
||||
snprintf(buffer, kBufferSize, " %s options:\n%s\n",
|
||||
block_based_table_factory_->Name(),
|
||||
block_based_table_factory_->GetPrintableTableOptions().c_str());
|
||||
if (block_based_table_factory_) {
|
||||
snprintf(
|
||||
buffer, kBufferSize, " %s options:\n%s\n",
|
||||
(block_based_table_factory_->Name() ? block_based_table_factory_->Name()
|
||||
: ""),
|
||||
block_based_table_factory_->GetPrintableTableOptions().c_str());
|
||||
ret.append(buffer);
|
||||
}
|
||||
if (!cuckoo_table_factory_) {
|
||||
if (cuckoo_table_factory_) {
|
||||
snprintf(buffer, kBufferSize, " %s options:\n%s\n",
|
||||
cuckoo_table_factory_->Name(),
|
||||
cuckoo_table_factory_->Name() ? cuckoo_table_factory_->Name() : "",
|
||||
cuckoo_table_factory_->GetPrintableTableOptions().c_str());
|
||||
ret.append(buffer);
|
||||
}
|
||||
|
||||
+13
-9
@@ -14,11 +14,12 @@
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "table/block_prefix_index.h"
|
||||
#include "table/block_hash_index.h"
|
||||
#include "table/block_prefix_index.h"
|
||||
#include "table/internal_iterator.h"
|
||||
|
||||
#include "format.h"
|
||||
@@ -151,15 +152,18 @@ class BlockIter : public InternalIterator {
|
||||
|
||||
virtual void SeekToLast() override;
|
||||
|
||||
virtual Status PinData() override {
|
||||
// block data is always pinned.
|
||||
return Status::OK();
|
||||
#ifndef NDEBUG
|
||||
~BlockIter() {
|
||||
// Assert that the BlockIter is never deleted while Pinning is Enabled.
|
||||
assert(!pinned_iters_mgr_ ||
|
||||
(pinned_iters_mgr_ && !pinned_iters_mgr_->PinningEnabled()));
|
||||
}
|
||||
|
||||
virtual Status ReleasePinnedData() override {
|
||||
// block data is always pinned.
|
||||
return Status::OK();
|
||||
virtual void SetPinnedItersMgr(
|
||||
PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
pinned_iters_mgr_ = pinned_iters_mgr;
|
||||
}
|
||||
PinnedIteratorsManager* pinned_iters_mgr_ = nullptr;
|
||||
#endif
|
||||
|
||||
virtual bool IsKeyPinned() const override { return key_.IsKeyPinned(); }
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/flush_block_policy.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
#include "table/block.h"
|
||||
@@ -315,6 +316,7 @@ bool GoodCompressionRatio(size_t compressed_size, size_t raw_size) {
|
||||
Slice CompressBlock(const Slice& raw,
|
||||
const CompressionOptions& compression_options,
|
||||
CompressionType* type, uint32_t format_version,
|
||||
const Slice& compression_dict,
|
||||
std::string* compressed_output) {
|
||||
if (*type == kNoCompression) {
|
||||
return raw;
|
||||
@@ -334,7 +336,7 @@ Slice CompressBlock(const Slice& raw,
|
||||
if (Zlib_Compress(
|
||||
compression_options,
|
||||
GetCompressFormatForVersion(kZlibCompression, format_version),
|
||||
raw.data(), raw.size(), compressed_output) &&
|
||||
raw.data(), raw.size(), compressed_output, compression_dict) &&
|
||||
GoodCompressionRatio(compressed_output->size(), raw.size())) {
|
||||
return *compressed_output;
|
||||
}
|
||||
@@ -352,7 +354,7 @@ Slice CompressBlock(const Slice& raw,
|
||||
if (LZ4_Compress(
|
||||
compression_options,
|
||||
GetCompressFormatForVersion(kLZ4Compression, format_version),
|
||||
raw.data(), raw.size(), compressed_output) &&
|
||||
raw.data(), raw.size(), compressed_output, compression_dict) &&
|
||||
GoodCompressionRatio(compressed_output->size(), raw.size())) {
|
||||
return *compressed_output;
|
||||
}
|
||||
@@ -361,14 +363,21 @@ Slice CompressBlock(const Slice& raw,
|
||||
if (LZ4HC_Compress(
|
||||
compression_options,
|
||||
GetCompressFormatForVersion(kLZ4HCCompression, format_version),
|
||||
raw.data(), raw.size(), compressed_output) &&
|
||||
raw.data(), raw.size(), compressed_output, compression_dict) &&
|
||||
GoodCompressionRatio(compressed_output->size(), raw.size())) {
|
||||
return *compressed_output;
|
||||
}
|
||||
break; // fall back to no compression.
|
||||
case kXpressCompression:
|
||||
if (XPRESS_Compress(raw.data(), raw.size(),
|
||||
compressed_output) &&
|
||||
GoodCompressionRatio(compressed_output->size(), raw.size())) {
|
||||
return *compressed_output;
|
||||
}
|
||||
break;
|
||||
case kZSTDNotFinalCompression:
|
||||
if (ZSTD_Compress(compression_options, raw.data(), raw.size(),
|
||||
compressed_output) &&
|
||||
compressed_output, compression_dict) &&
|
||||
GoodCompressionRatio(compressed_output->size(), raw.size())) {
|
||||
return *compressed_output;
|
||||
}
|
||||
@@ -461,6 +470,8 @@ struct BlockBasedTableBuilder::Rep {
|
||||
std::string last_key;
|
||||
const CompressionType compression_type;
|
||||
const CompressionOptions compression_opts;
|
||||
// Data for presetting the compression library's dictionary, or nullptr.
|
||||
const std::string* compression_dict;
|
||||
TableProperties props;
|
||||
|
||||
bool closed = false; // Either Finish() or Abandon() has been called.
|
||||
@@ -484,7 +495,8 @@ struct BlockBasedTableBuilder::Rep {
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t _column_family_id, WritableFileWriter* f,
|
||||
const CompressionType _compression_type,
|
||||
const CompressionOptions& _compression_opts, const bool skip_filters,
|
||||
const CompressionOptions& _compression_opts,
|
||||
const std::string* _compression_dict, const bool skip_filters,
|
||||
const std::string& _column_family_name)
|
||||
: ioptions(_ioptions),
|
||||
table_options(table_opt),
|
||||
@@ -499,6 +511,7 @@ struct BlockBasedTableBuilder::Rep {
|
||||
table_options.index_block_restart_interval)),
|
||||
compression_type(_compression_type),
|
||||
compression_opts(_compression_opts),
|
||||
compression_dict(_compression_dict),
|
||||
filter_block(skip_filters ? nullptr : CreateFilterBlockBuilder(
|
||||
_ioptions, table_options)),
|
||||
flush_block_policy(
|
||||
@@ -525,7 +538,8 @@ BlockBasedTableBuilder::BlockBasedTableBuilder(
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, WritableFileWriter* file,
|
||||
const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, const bool skip_filters,
|
||||
const CompressionOptions& compression_opts,
|
||||
const std::string* compression_dict, const bool skip_filters,
|
||||
const std::string& column_family_name) {
|
||||
BlockBasedTableOptions sanitized_table_options(table_options);
|
||||
if (sanitized_table_options.format_version == 0 &&
|
||||
@@ -540,8 +554,8 @@ BlockBasedTableBuilder::BlockBasedTableBuilder(
|
||||
|
||||
rep_ = new Rep(ioptions, sanitized_table_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id, file,
|
||||
compression_type, compression_opts, skip_filters,
|
||||
column_family_name);
|
||||
compression_type, compression_opts, compression_dict,
|
||||
skip_filters, column_family_name);
|
||||
|
||||
if (rep_->filter_block != nullptr) {
|
||||
rep_->filter_block->StartBlock(0);
|
||||
@@ -606,7 +620,7 @@ void BlockBasedTableBuilder::Flush() {
|
||||
assert(!r->closed);
|
||||
if (!ok()) return;
|
||||
if (r->data_block.empty()) return;
|
||||
WriteBlock(&r->data_block, &r->pending_handle);
|
||||
WriteBlock(&r->data_block, &r->pending_handle, true /* is_data_block */);
|
||||
if (ok() && !r->table_options.skip_table_builder_flush) {
|
||||
r->status = r->file->Flush();
|
||||
}
|
||||
@@ -618,13 +632,15 @@ void BlockBasedTableBuilder::Flush() {
|
||||
}
|
||||
|
||||
void BlockBasedTableBuilder::WriteBlock(BlockBuilder* block,
|
||||
BlockHandle* handle) {
|
||||
WriteBlock(block->Finish(), handle);
|
||||
BlockHandle* handle,
|
||||
bool is_data_block) {
|
||||
WriteBlock(block->Finish(), handle, is_data_block);
|
||||
block->Reset();
|
||||
}
|
||||
|
||||
void BlockBasedTableBuilder::WriteBlock(const Slice& raw_block_contents,
|
||||
BlockHandle* handle) {
|
||||
BlockHandle* handle,
|
||||
bool is_data_block) {
|
||||
// File format contains a sequence of blocks where each block has:
|
||||
// block_data: uint8[n]
|
||||
// type: uint8
|
||||
@@ -635,9 +651,13 @@ void BlockBasedTableBuilder::WriteBlock(const Slice& raw_block_contents,
|
||||
auto type = r->compression_type;
|
||||
Slice block_contents;
|
||||
if (raw_block_contents.size() < kCompressionSizeLimit) {
|
||||
block_contents =
|
||||
CompressBlock(raw_block_contents, r->compression_opts, &type,
|
||||
r->table_options.format_version, &r->compressed_output);
|
||||
Slice compression_dict;
|
||||
if (is_data_block && r->compression_dict && r->compression_dict->size()) {
|
||||
compression_dict = *r->compression_dict;
|
||||
}
|
||||
block_contents = CompressBlock(raw_block_contents, r->compression_opts,
|
||||
&type, r->table_options.format_version,
|
||||
compression_dict, &r->compressed_output);
|
||||
} else {
|
||||
RecordTick(r->ioptions.statistics, NUMBER_BLOCK_NOT_COMPRESSED);
|
||||
type = kNoCompression;
|
||||
@@ -745,7 +765,8 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
assert(!r->closed);
|
||||
r->closed = true;
|
||||
|
||||
BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle;
|
||||
BlockHandle filter_block_handle, metaindex_block_handle, index_block_handle,
|
||||
compression_dict_block_handle;
|
||||
// Write filter block
|
||||
if (ok() && r->filter_block != nullptr) {
|
||||
auto filter_contents = r->filter_block->Finish();
|
||||
@@ -776,7 +797,7 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
MetaIndexBuilder meta_index_builder;
|
||||
for (const auto& item : index_blocks.meta_blocks) {
|
||||
BlockHandle block_handle;
|
||||
WriteBlock(item.second, &block_handle);
|
||||
WriteBlock(item.second, &block_handle, false /* is_data_block */);
|
||||
meta_index_builder.Add(item.first, block_handle);
|
||||
}
|
||||
|
||||
@@ -794,7 +815,7 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
meta_index_builder.Add(key, filter_block_handle);
|
||||
}
|
||||
|
||||
// Write properties block.
|
||||
// Write properties and compression dictionary blocks.
|
||||
{
|
||||
PropertyBlockBuilder property_block_builder;
|
||||
r->props.column_family_id = r->column_family_id;
|
||||
@@ -803,6 +824,25 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
r->table_options.filter_policy->Name() : "";
|
||||
r->props.index_size =
|
||||
r->index_builder->EstimatedSize() + kBlockTrailerSize;
|
||||
r->props.comparator_name = r->ioptions.comparator != nullptr
|
||||
? r->ioptions.comparator->Name()
|
||||
: "nullptr";
|
||||
r->props.merge_operator_name = r->ioptions.merge_operator != nullptr
|
||||
? r->ioptions.merge_operator->Name()
|
||||
: "nullptr";
|
||||
|
||||
std::string property_collectors_names = "[";
|
||||
property_collectors_names = "[";
|
||||
for (size_t i = 0;
|
||||
i < r->ioptions.table_properties_collector_factories.size(); ++i) {
|
||||
if (i != 0) {
|
||||
property_collectors_names += ",";
|
||||
}
|
||||
property_collectors_names +=
|
||||
r->ioptions.table_properties_collector_factories[i]->Name();
|
||||
}
|
||||
property_collectors_names += "]";
|
||||
r->props.property_collectors_names = property_collectors_names;
|
||||
|
||||
// Add basic properties
|
||||
property_block_builder.AddTableProperty(r->props);
|
||||
@@ -818,9 +858,16 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
kNoCompression,
|
||||
&properties_block_handle
|
||||
);
|
||||
|
||||
meta_index_builder.Add(kPropertiesBlock, properties_block_handle);
|
||||
} // end of properties block writing
|
||||
|
||||
// Write compression dictionary block
|
||||
if (r->compression_dict && r->compression_dict->size()) {
|
||||
WriteRawBlock(*r->compression_dict, kNoCompression,
|
||||
&compression_dict_block_handle);
|
||||
meta_index_builder.Add(kCompressionDictBlock,
|
||||
compression_dict_block_handle);
|
||||
}
|
||||
} // end of properties/compression dictionary block writing
|
||||
} // meta blocks
|
||||
|
||||
// Write index block
|
||||
@@ -828,7 +875,8 @@ Status BlockBasedTableBuilder::Finish() {
|
||||
// flush the meta index block
|
||||
WriteRawBlock(meta_index_builder.Finish(), kNoCompression,
|
||||
&metaindex_block_handle);
|
||||
WriteBlock(index_blocks.index_block_contents, &index_block_handle);
|
||||
WriteBlock(index_blocks.index_block_contents, &index_block_handle,
|
||||
false /* is_data_block */);
|
||||
}
|
||||
|
||||
// Write footer
|
||||
|
||||
@@ -34,6 +34,8 @@ class BlockBasedTableBuilder : public TableBuilder {
|
||||
// Create a builder that will store the contents of the table it is
|
||||
// building in *file. Does not close the file. It is up to the
|
||||
// caller to close the file after calling Finish().
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary, or nullptr.
|
||||
BlockBasedTableBuilder(
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const BlockBasedTableOptions& table_options,
|
||||
@@ -42,7 +44,8 @@ class BlockBasedTableBuilder : public TableBuilder {
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, WritableFileWriter* file,
|
||||
const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, const bool skip_filters,
|
||||
const CompressionOptions& compression_opts,
|
||||
const std::string* compression_dict, const bool skip_filters,
|
||||
const std::string& column_family_name);
|
||||
|
||||
// REQUIRES: Either Finish() or Abandon() has been called.
|
||||
@@ -82,11 +85,14 @@ class BlockBasedTableBuilder : public TableBuilder {
|
||||
|
||||
private:
|
||||
bool ok() const { return status().ok(); }
|
||||
|
||||
// Call block's Finish() method and then write the finalize block contents to
|
||||
// file.
|
||||
void WriteBlock(BlockBuilder* block, BlockHandle* handle);
|
||||
void WriteBlock(BlockBuilder* block, BlockHandle* handle, bool is_data_block);
|
||||
|
||||
// Directly write block content to the file.
|
||||
void WriteBlock(const Slice& block_contents, BlockHandle* handle);
|
||||
void WriteBlock(const Slice& block_contents, BlockHandle* handle,
|
||||
bool is_data_block);
|
||||
void WriteRawBlock(const Slice& data, CompressionType, BlockHandle* handle);
|
||||
Status InsertBlockInCache(const Slice& block_contents,
|
||||
const CompressionType type,
|
||||
|
||||
@@ -76,6 +76,7 @@ TableBuilder* BlockBasedTableFactory::NewTableBuilder(
|
||||
table_builder_options.int_tbl_prop_collector_factories, column_family_id,
|
||||
file, table_builder_options.compression_type,
|
||||
table_builder_options.compression_opts,
|
||||
table_builder_options.compression_dict,
|
||||
table_builder_options.skip_filters,
|
||||
table_builder_options.column_family_name);
|
||||
|
||||
|
||||
@@ -64,13 +64,16 @@ const size_t kMaxCacheKeyPrefixSize __attribute__((unused)) =
|
||||
// The only relevant option is options.verify_checksums for now.
|
||||
// On failure return non-OK.
|
||||
// On success fill *result and return OK - caller owns *result
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary.
|
||||
Status ReadBlockFromFile(RandomAccessFileReader* file, const Footer& footer,
|
||||
const ReadOptions& options, const BlockHandle& handle,
|
||||
std::unique_ptr<Block>* result, Env* env,
|
||||
bool do_uncompress = true) {
|
||||
bool do_uncompress = true,
|
||||
const Slice& compression_dict = Slice()) {
|
||||
BlockContents contents;
|
||||
Status s = ReadBlockContents(file, footer, options, handle, &contents, env,
|
||||
do_uncompress);
|
||||
do_uncompress, compression_dict);
|
||||
if (s.ok()) {
|
||||
result->reset(new Block(std::move(contents)));
|
||||
}
|
||||
@@ -407,6 +410,11 @@ struct BlockBasedTable::Rep {
|
||||
BlockHandle filter_handle;
|
||||
|
||||
std::shared_ptr<const TableProperties> table_properties;
|
||||
// Block containing the data for the compression dictionary. We take ownership
|
||||
// for the entire block struct, even though we only use its Slice member. This
|
||||
// is easier because the Slice member depends on the continued existence of
|
||||
// another member ("allocation").
|
||||
std::unique_ptr<const BlockContents> compression_dict_block;
|
||||
BlockBasedTableOptions::IndexType index_type;
|
||||
bool hash_index_allow_collision;
|
||||
bool whole_key_filtering;
|
||||
@@ -585,6 +593,31 @@ Status BlockBasedTable::Open(const ImmutableCFOptions& ioptions,
|
||||
"Cannot find Properties block from file.");
|
||||
}
|
||||
|
||||
// Read the compression dictionary meta block
|
||||
bool found_compression_dict;
|
||||
s = SeekToCompressionDictBlock(meta_iter.get(), &found_compression_dict);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, rep->ioptions.info_log,
|
||||
"Cannot seek to compression dictionary block from file: %s",
|
||||
s.ToString().c_str());
|
||||
} else if (found_compression_dict) {
|
||||
// TODO(andrewkr): Add to block cache if cache_index_and_filter_blocks is
|
||||
// true.
|
||||
unique_ptr<BlockContents> compression_dict_block{new BlockContents()};
|
||||
s = rocksdb::ReadMetaBlock(rep->file.get(), file_size,
|
||||
kBlockBasedTableMagicNumber, rep->ioptions.env,
|
||||
rocksdb::kCompressionDictBlock,
|
||||
compression_dict_block.get());
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, rep->ioptions.info_log,
|
||||
"Encountered error while reading data from compression dictionary "
|
||||
"block %s",
|
||||
s.ToString().c_str());
|
||||
} else {
|
||||
rep->compression_dict_block = std::move(compression_dict_block);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine whether whole key filtering is supported.
|
||||
if (rep->table_properties) {
|
||||
rep->whole_key_filtering &=
|
||||
@@ -727,7 +760,8 @@ Status BlockBasedTable::GetDataBlockFromCache(
|
||||
const Slice& block_cache_key, const Slice& compressed_block_cache_key,
|
||||
Cache* block_cache, Cache* block_cache_compressed, Statistics* statistics,
|
||||
const ReadOptions& read_options,
|
||||
BlockBasedTable::CachableEntry<Block>* block, uint32_t format_version) {
|
||||
BlockBasedTable::CachableEntry<Block>* block, uint32_t format_version,
|
||||
const Slice& compression_dict) {
|
||||
Status s;
|
||||
Block* compressed_block = nullptr;
|
||||
Cache::Handle* block_cache_compressed_handle = nullptr;
|
||||
@@ -771,7 +805,7 @@ Status BlockBasedTable::GetDataBlockFromCache(
|
||||
BlockContents contents;
|
||||
s = UncompressBlockContents(compressed_block->data(),
|
||||
compressed_block->size(), &contents,
|
||||
format_version);
|
||||
format_version, compression_dict);
|
||||
|
||||
// Insert uncompressed block into block cache
|
||||
if (s.ok()) {
|
||||
@@ -801,7 +835,8 @@ Status BlockBasedTable::PutDataBlockToCache(
|
||||
const Slice& block_cache_key, const Slice& compressed_block_cache_key,
|
||||
Cache* block_cache, Cache* block_cache_compressed,
|
||||
const ReadOptions& read_options, Statistics* statistics,
|
||||
CachableEntry<Block>* block, Block* raw_block, uint32_t format_version) {
|
||||
CachableEntry<Block>* block, Block* raw_block, uint32_t format_version,
|
||||
const Slice& compression_dict) {
|
||||
assert(raw_block->compression_type() == kNoCompression ||
|
||||
block_cache_compressed != nullptr);
|
||||
|
||||
@@ -810,7 +845,7 @@ Status BlockBasedTable::PutDataBlockToCache(
|
||||
BlockContents contents;
|
||||
if (raw_block->compression_type() != kNoCompression) {
|
||||
s = UncompressBlockContents(raw_block->data(), raw_block->size(), &contents,
|
||||
format_version);
|
||||
format_version, compression_dict);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
delete raw_block;
|
||||
@@ -1078,6 +1113,10 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
}
|
||||
}
|
||||
|
||||
Slice compression_dict;
|
||||
if (rep->compression_dict_block) {
|
||||
compression_dict = rep->compression_dict_block->data;
|
||||
}
|
||||
// If either block cache is enabled, we'll try to read from it.
|
||||
if (block_cache != nullptr || block_cache_compressed != nullptr) {
|
||||
Statistics* statistics = rep->ioptions.statistics;
|
||||
@@ -1098,9 +1137,9 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
compressed_cache_key);
|
||||
}
|
||||
|
||||
s = GetDataBlockFromCache(key, ckey, block_cache, block_cache_compressed,
|
||||
statistics, ro, &block,
|
||||
rep->table_options.format_version);
|
||||
s = GetDataBlockFromCache(
|
||||
key, ckey, block_cache, block_cache_compressed, statistics, ro, &block,
|
||||
rep->table_options.format_version, compression_dict);
|
||||
|
||||
if (block.value == nullptr && !no_io && ro.fill_cache) {
|
||||
std::unique_ptr<Block> raw_block;
|
||||
@@ -1108,13 +1147,15 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
StopWatch sw(rep->ioptions.env, statistics, READ_BLOCK_GET_MICROS);
|
||||
s = ReadBlockFromFile(rep->file.get(), rep->footer, ro, handle,
|
||||
&raw_block, rep->ioptions.env,
|
||||
block_cache_compressed == nullptr);
|
||||
block_cache_compressed == nullptr,
|
||||
compression_dict);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
s = PutDataBlockToCache(key, ckey, block_cache, block_cache_compressed,
|
||||
ro, statistics, &block, raw_block.release(),
|
||||
rep->table_options.format_version);
|
||||
rep->table_options.format_version,
|
||||
compression_dict);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1132,7 +1173,8 @@ InternalIterator* BlockBasedTable::NewDataBlockIterator(
|
||||
}
|
||||
std::unique_ptr<Block> block_value;
|
||||
s = ReadBlockFromFile(rep->file.get(), rep->footer, ro, handle,
|
||||
&block_value, rep->ioptions.env);
|
||||
&block_value, rep->ioptions.env,
|
||||
true /* do_uncompress */, compression_dict);
|
||||
if (s.ok()) {
|
||||
block.value = block_value.release();
|
||||
}
|
||||
@@ -1224,45 +1266,45 @@ bool BlockBasedTable::PrefixMayMatch(const Slice& internal_key) {
|
||||
// First, try check with full filter
|
||||
auto filter_entry = GetFilter(true /* no io */);
|
||||
FilterBlockReader* filter = filter_entry.value;
|
||||
if (filter != nullptr && !filter->IsBlockBased()) {
|
||||
may_match = filter->PrefixMayMatch(prefix);
|
||||
}
|
||||
if (filter != nullptr) {
|
||||
if (!filter->IsBlockBased()) {
|
||||
may_match = filter->PrefixMayMatch(prefix);
|
||||
} else {
|
||||
// Then, try find it within each block
|
||||
unique_ptr<InternalIterator> iiter(NewIndexIterator(no_io_read_options));
|
||||
iiter->Seek(internal_prefix);
|
||||
|
||||
// Then, try find it within each block
|
||||
if (may_match) {
|
||||
unique_ptr<InternalIterator> iiter(NewIndexIterator(no_io_read_options));
|
||||
iiter->Seek(internal_prefix);
|
||||
|
||||
if (!iiter->Valid()) {
|
||||
// we're past end of file
|
||||
// if it's incomplete, it means that we avoided I/O
|
||||
// and we're not really sure that we're past the end
|
||||
// of the file
|
||||
may_match = iiter->status().IsIncomplete();
|
||||
} else if (ExtractUserKey(iiter->key()).starts_with(
|
||||
ExtractUserKey(internal_prefix))) {
|
||||
// we need to check for this subtle case because our only
|
||||
// guarantee is that "the key is a string >= last key in that data
|
||||
// block" according to the doc/table_format.txt spec.
|
||||
//
|
||||
// Suppose iiter->key() starts with the desired prefix; it is not
|
||||
// necessarily the case that the corresponding data block will
|
||||
// contain the prefix, since iiter->key() need not be in the
|
||||
// block. However, the next data block may contain the prefix, so
|
||||
// we return true to play it safe.
|
||||
may_match = true;
|
||||
} else if (filter != nullptr && filter->IsBlockBased()) {
|
||||
// iiter->key() does NOT start with the desired prefix. Because
|
||||
// Seek() finds the first key that is >= the seek target, this
|
||||
// means that iiter->key() > prefix. Thus, any data blocks coming
|
||||
// after the data block corresponding to iiter->key() cannot
|
||||
// possibly contain the key. Thus, the corresponding data block
|
||||
// is the only on could potentially contain the prefix.
|
||||
Slice handle_value = iiter->value();
|
||||
BlockHandle handle;
|
||||
s = handle.DecodeFrom(&handle_value);
|
||||
assert(s.ok());
|
||||
may_match = filter->PrefixMayMatch(prefix, handle.offset());
|
||||
if (!iiter->Valid()) {
|
||||
// we're past end of file
|
||||
// if it's incomplete, it means that we avoided I/O
|
||||
// and we're not really sure that we're past the end
|
||||
// of the file
|
||||
may_match = iiter->status().IsIncomplete();
|
||||
} else if (ExtractUserKey(iiter->key())
|
||||
.starts_with(ExtractUserKey(internal_prefix))) {
|
||||
// we need to check for this subtle case because our only
|
||||
// guarantee is that "the key is a string >= last key in that data
|
||||
// block" according to the doc/table_format.txt spec.
|
||||
//
|
||||
// Suppose iiter->key() starts with the desired prefix; it is not
|
||||
// necessarily the case that the corresponding data block will
|
||||
// contain the prefix, since iiter->key() need not be in the
|
||||
// block. However, the next data block may contain the prefix, so
|
||||
// we return true to play it safe.
|
||||
may_match = true;
|
||||
} else if (filter->IsBlockBased()) {
|
||||
// iiter->key() does NOT start with the desired prefix. Because
|
||||
// Seek() finds the first key that is >= the seek target, this
|
||||
// means that iiter->key() > prefix. Thus, any data blocks coming
|
||||
// after the data block corresponding to iiter->key() cannot
|
||||
// possibly contain the key. Thus, the corresponding data block
|
||||
// is the only on could potentially contain the prefix.
|
||||
Slice handle_value = iiter->value();
|
||||
BlockHandle handle;
|
||||
s = handle.DecodeFrom(&handle_value);
|
||||
assert(s.ok());
|
||||
may_match = filter->PrefixMayMatch(prefix, handle.offset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1290,7 +1332,8 @@ InternalIterator* BlockBasedTable::NewIterator(const ReadOptions& read_options,
|
||||
NewIndexIterator(read_options), arena);
|
||||
}
|
||||
|
||||
bool BlockBasedTable::FullFilterKeyMayMatch(FilterBlockReader* filter,
|
||||
bool BlockBasedTable::FullFilterKeyMayMatch(const ReadOptions& read_options,
|
||||
FilterBlockReader* filter,
|
||||
const Slice& internal_key) const {
|
||||
if (filter == nullptr || filter->IsBlockBased()) {
|
||||
return true;
|
||||
@@ -1299,7 +1342,7 @@ bool BlockBasedTable::FullFilterKeyMayMatch(FilterBlockReader* filter,
|
||||
if (!filter->KeyMayMatch(user_key)) {
|
||||
return false;
|
||||
}
|
||||
if (rep_->ioptions.prefix_extractor &&
|
||||
if (!read_options.total_order_seek && rep_->ioptions.prefix_extractor &&
|
||||
rep_->ioptions.prefix_extractor->InDomain(user_key) &&
|
||||
!filter->PrefixMayMatch(
|
||||
rep_->ioptions.prefix_extractor->Transform(user_key))) {
|
||||
@@ -1319,7 +1362,7 @@ Status BlockBasedTable::Get(const ReadOptions& read_options, const Slice& key,
|
||||
|
||||
// First check the full filter
|
||||
// If full filter not useful, Then go into each block
|
||||
if (!FullFilterKeyMayMatch(filter, key)) {
|
||||
if (!FullFilterKeyMayMatch(read_options, filter, key)) {
|
||||
RecordTick(rep_->ioptions.statistics, BLOOM_FILTER_USEFUL);
|
||||
} else {
|
||||
BlockIter iiter;
|
||||
@@ -1454,8 +1497,10 @@ bool BlockBasedTable::TEST_KeyInCache(const ReadOptions& options,
|
||||
Slice ckey;
|
||||
|
||||
s = GetDataBlockFromCache(cache_key, ckey, block_cache, nullptr, nullptr,
|
||||
options, &block,
|
||||
rep_->table_options.format_version);
|
||||
options, &block, rep_->table_options.format_version,
|
||||
rep_->compression_dict_block
|
||||
? rep_->compression_dict_block->data
|
||||
: Slice());
|
||||
assert(s.ok());
|
||||
bool in_cache = block.value != nullptr;
|
||||
if (in_cache) {
|
||||
@@ -1605,6 +1650,10 @@ Status BlockBasedTable::DumpTable(WritableFile* out_file) {
|
||||
out_file->Append(" Properties block handle: ");
|
||||
out_file->Append(meta_iter->value().ToString(true).c_str());
|
||||
out_file->Append("\n");
|
||||
} else if (meta_iter->key() == rocksdb::kCompressionDictBlock) {
|
||||
out_file->Append(" Compression dictionary block handle: ");
|
||||
out_file->Append(meta_iter->value().ToString(true).c_str());
|
||||
out_file->Append("\n");
|
||||
} else if (strstr(meta_iter->key().ToString().c_str(),
|
||||
"filter.rocksdb.") != nullptr) {
|
||||
out_file->Append(" Filter block handle: ");
|
||||
|
||||
@@ -92,7 +92,7 @@ class BlockBasedTable : public TableReader {
|
||||
GetContext* get_context, bool skip_filters = false) override;
|
||||
|
||||
// Pre-fetch the disk blocks that correspond to the key range specified by
|
||||
// (kbegin, kend). The call will return return error status in the event of
|
||||
// (kbegin, kend). The call will return error status in the event of
|
||||
// IO or iteration error.
|
||||
Status Prefetch(const Slice* begin, const Slice* end) override;
|
||||
|
||||
@@ -165,11 +165,15 @@ class BlockBasedTable : public TableReader {
|
||||
// block_cache_compressed.
|
||||
// On success, Status::OK with be returned and @block will be populated with
|
||||
// pointer to the block as well as its block handle.
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary.
|
||||
static Status GetDataBlockFromCache(
|
||||
const Slice& block_cache_key, const Slice& compressed_block_cache_key,
|
||||
Cache* block_cache, Cache* block_cache_compressed, Statistics* statistics,
|
||||
const ReadOptions& read_options,
|
||||
BlockBasedTable::CachableEntry<Block>* block, uint32_t format_version);
|
||||
BlockBasedTable::CachableEntry<Block>* block, uint32_t format_version,
|
||||
const Slice& compression_dict);
|
||||
|
||||
// Put a raw block (maybe compressed) to the corresponding block caches.
|
||||
// This method will perform decompression against raw_block if needed and then
|
||||
// populate the block caches.
|
||||
@@ -178,11 +182,14 @@ class BlockBasedTable : public TableReader {
|
||||
//
|
||||
// REQUIRES: raw_block is heap-allocated. PutDataBlockToCache() will be
|
||||
// responsible for releasing its memory if error occurs.
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary.
|
||||
static Status PutDataBlockToCache(
|
||||
const Slice& block_cache_key, const Slice& compressed_block_cache_key,
|
||||
Cache* block_cache, Cache* block_cache_compressed,
|
||||
const ReadOptions& read_options, Statistics* statistics,
|
||||
CachableEntry<Block>* block, Block* raw_block, uint32_t format_version);
|
||||
CachableEntry<Block>* block, Block* raw_block, uint32_t format_version,
|
||||
const Slice& compression_dict);
|
||||
|
||||
// Calls (*handle_result)(arg, ...) repeatedly, starting with the entry found
|
||||
// after a call to Seek(key), until handle_result returns false.
|
||||
@@ -200,7 +207,8 @@ class BlockBasedTable : public TableReader {
|
||||
IndexReader** index_reader,
|
||||
InternalIterator* preloaded_meta_index_iter = nullptr);
|
||||
|
||||
bool FullFilterKeyMayMatch(FilterBlockReader* filter,
|
||||
bool FullFilterKeyMayMatch(const ReadOptions& read_options,
|
||||
FilterBlockReader* filter,
|
||||
const Slice& user_key) const;
|
||||
|
||||
// Read the meta block from sst.
|
||||
|
||||
+28
-14
@@ -296,7 +296,8 @@ Status ReadBlock(RandomAccessFileReader* file, const Footer& footer,
|
||||
Status ReadBlockContents(RandomAccessFileReader* file, const Footer& footer,
|
||||
const ReadOptions& options, const BlockHandle& handle,
|
||||
BlockContents* contents, Env* env,
|
||||
bool decompression_requested) {
|
||||
bool decompression_requested,
|
||||
const Slice& compression_dict) {
|
||||
Status status;
|
||||
Slice slice;
|
||||
size_t n = static_cast<size_t>(handle.size());
|
||||
@@ -326,7 +327,8 @@ Status ReadBlockContents(RandomAccessFileReader* file, const Footer& footer,
|
||||
compression_type = static_cast<rocksdb::CompressionType>(slice.data()[n]);
|
||||
|
||||
if (decompression_requested && compression_type != kNoCompression) {
|
||||
return UncompressBlockContents(slice.data(), n, contents, footer.version());
|
||||
return UncompressBlockContents(slice.data(), n, contents, footer.version(),
|
||||
compression_dict);
|
||||
}
|
||||
|
||||
if (slice.data() != used_buf) {
|
||||
@@ -351,8 +353,8 @@ Status ReadBlockContents(RandomAccessFileReader* file, const Footer& footer,
|
||||
// free this buffer.
|
||||
// format_version is the block format as defined in include/rocksdb/table.h
|
||||
Status UncompressBlockContents(const char* data, size_t n,
|
||||
BlockContents* contents,
|
||||
uint32_t format_version) {
|
||||
BlockContents* contents, uint32_t format_version,
|
||||
const Slice& compression_dict) {
|
||||
std::unique_ptr<char[]> ubuf;
|
||||
int decompress_size = 0;
|
||||
assert(data[n] != kNoCompression);
|
||||
@@ -364,7 +366,7 @@ Status UncompressBlockContents(const char* data, size_t n,
|
||||
if (!Snappy_GetUncompressedLength(data, n, &ulength)) {
|
||||
return Status::Corruption(snappy_corrupt_msg);
|
||||
}
|
||||
ubuf = std::unique_ptr<char[]>(new char[ulength]);
|
||||
ubuf.reset(new char[ulength]);
|
||||
if (!Snappy_Uncompress(data, n, ubuf.get())) {
|
||||
return Status::Corruption(snappy_corrupt_msg);
|
||||
}
|
||||
@@ -372,9 +374,10 @@ Status UncompressBlockContents(const char* data, size_t n,
|
||||
break;
|
||||
}
|
||||
case kZlibCompression:
|
||||
ubuf = std::unique_ptr<char[]>(Zlib_Uncompress(
|
||||
ubuf.reset(Zlib_Uncompress(
|
||||
data, n, &decompress_size,
|
||||
GetCompressFormatForVersion(kZlibCompression, format_version)));
|
||||
GetCompressFormatForVersion(kZlibCompression, format_version),
|
||||
compression_dict));
|
||||
if (!ubuf) {
|
||||
static char zlib_corrupt_msg[] =
|
||||
"Zlib not supported or corrupted Zlib compressed block contents";
|
||||
@@ -384,7 +387,7 @@ Status UncompressBlockContents(const char* data, size_t n,
|
||||
BlockContents(std::move(ubuf), decompress_size, true, kNoCompression);
|
||||
break;
|
||||
case kBZip2Compression:
|
||||
ubuf = std::unique_ptr<char[]>(BZip2_Uncompress(
|
||||
ubuf.reset(BZip2_Uncompress(
|
||||
data, n, &decompress_size,
|
||||
GetCompressFormatForVersion(kBZip2Compression, format_version)));
|
||||
if (!ubuf) {
|
||||
@@ -396,9 +399,10 @@ Status UncompressBlockContents(const char* data, size_t n,
|
||||
BlockContents(std::move(ubuf), decompress_size, true, kNoCompression);
|
||||
break;
|
||||
case kLZ4Compression:
|
||||
ubuf = std::unique_ptr<char[]>(LZ4_Uncompress(
|
||||
ubuf.reset(LZ4_Uncompress(
|
||||
data, n, &decompress_size,
|
||||
GetCompressFormatForVersion(kLZ4Compression, format_version)));
|
||||
GetCompressFormatForVersion(kLZ4Compression, format_version),
|
||||
compression_dict));
|
||||
if (!ubuf) {
|
||||
static char lz4_corrupt_msg[] =
|
||||
"LZ4 not supported or corrupted LZ4 compressed block contents";
|
||||
@@ -408,9 +412,10 @@ Status UncompressBlockContents(const char* data, size_t n,
|
||||
BlockContents(std::move(ubuf), decompress_size, true, kNoCompression);
|
||||
break;
|
||||
case kLZ4HCCompression:
|
||||
ubuf = std::unique_ptr<char[]>(LZ4_Uncompress(
|
||||
ubuf.reset(LZ4_Uncompress(
|
||||
data, n, &decompress_size,
|
||||
GetCompressFormatForVersion(kLZ4HCCompression, format_version)));
|
||||
GetCompressFormatForVersion(kLZ4HCCompression, format_version),
|
||||
compression_dict));
|
||||
if (!ubuf) {
|
||||
static char lz4hc_corrupt_msg[] =
|
||||
"LZ4HC not supported or corrupted LZ4HC compressed block contents";
|
||||
@@ -419,9 +424,18 @@ Status UncompressBlockContents(const char* data, size_t n,
|
||||
*contents =
|
||||
BlockContents(std::move(ubuf), decompress_size, true, kNoCompression);
|
||||
break;
|
||||
case kXpressCompression:
|
||||
ubuf.reset(XPRESS_Uncompress(data, n, &decompress_size));
|
||||
if (!ubuf) {
|
||||
static char xpress_corrupt_msg[] =
|
||||
"XPRESS not supported or corrupted XPRESS compressed block contents";
|
||||
return Status::Corruption(xpress_corrupt_msg);
|
||||
}
|
||||
*contents =
|
||||
BlockContents(std::move(ubuf), decompress_size, true, kNoCompression);
|
||||
break;
|
||||
case kZSTDNotFinalCompression:
|
||||
ubuf =
|
||||
std::unique_ptr<char[]>(ZSTD_Uncompress(data, n, &decompress_size));
|
||||
ubuf.reset(ZSTD_Uncompress(data, n, &decompress_size, compression_dict));
|
||||
if (!ubuf) {
|
||||
static char zstd_corrupt_msg[] =
|
||||
"ZSTD not supported or corrupted ZSTD compressed block contents";
|
||||
|
||||
+8
-3
@@ -15,6 +15,8 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
|
||||
#include "port/port.h" // noexcept
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Block;
|
||||
@@ -69,6 +71,7 @@ inline uint32_t GetCompressFormatForVersion(CompressionType compression_type,
|
||||
uint32_t version) {
|
||||
// snappy is not versioned
|
||||
assert(compression_type != kSnappyCompression &&
|
||||
compression_type != kXpressCompression &&
|
||||
compression_type != kNoCompression);
|
||||
// As of version 2, we encode compressed block with
|
||||
// compress_format_version == 2. Before that, the version is 1.
|
||||
@@ -192,7 +195,7 @@ struct BlockContents {
|
||||
compression_type(_compression_type),
|
||||
allocation(std::move(_data)) {}
|
||||
|
||||
BlockContents(BlockContents&& other) { *this = std::move(other); }
|
||||
BlockContents(BlockContents&& other) ROCKSDB_NOEXCEPT { *this = std::move(other); }
|
||||
|
||||
BlockContents& operator=(BlockContents&& other) {
|
||||
data = std::move(other.data);
|
||||
@@ -210,7 +213,8 @@ extern Status ReadBlockContents(RandomAccessFileReader* file,
|
||||
const ReadOptions& options,
|
||||
const BlockHandle& handle,
|
||||
BlockContents* contents, Env* env,
|
||||
bool do_uncompress);
|
||||
bool do_uncompress,
|
||||
const Slice& compression_dict = Slice());
|
||||
|
||||
// The 'data' points to the raw block contents read in from file.
|
||||
// This method allocates a new heap buffer and the raw block
|
||||
@@ -221,7 +225,8 @@ extern Status ReadBlockContents(RandomAccessFileReader* file,
|
||||
// util/compression.h
|
||||
extern Status UncompressBlockContents(const char* data, size_t n,
|
||||
BlockContents* contents,
|
||||
uint32_t compress_format_version);
|
||||
uint32_t compress_format_version,
|
||||
const Slice& compression_dict);
|
||||
|
||||
// Implementation details follow. Clients should ignore,
|
||||
|
||||
|
||||
+12
-11
@@ -12,6 +12,8 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class PinnedIteratorsManager;
|
||||
|
||||
class InternalIterator : public Cleanable {
|
||||
public:
|
||||
InternalIterator() {}
|
||||
@@ -61,20 +63,19 @@ class InternalIterator : public Cleanable {
|
||||
// satisfied without doing some IO, then this returns Status::Incomplete().
|
||||
virtual Status status() const = 0;
|
||||
|
||||
// Make sure that all current and future data blocks used by this iterator
|
||||
// will be pinned in memory and will not be released except when
|
||||
// ReleasePinnedData() is called or the iterator is deleted.
|
||||
virtual Status PinData() { return Status::NotSupported(""); }
|
||||
// Pass the PinnedIteratorsManager to the Iterator, most Iterators dont
|
||||
// communicate with PinnedIteratorsManager so default implementation is no-op
|
||||
// but for Iterators that need to communicate with PinnedIteratorsManager
|
||||
// they will implement this function and use the passed pointer to communicate
|
||||
// with PinnedIteratorsManager.
|
||||
virtual void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) {}
|
||||
|
||||
// Release all blocks that were pinned because of PinData() and no future
|
||||
// blocks will be pinned.
|
||||
virtual Status ReleasePinnedData() { return Status::NotSupported(""); }
|
||||
|
||||
// If true, this means that the Slice returned by key() is valid as long
|
||||
// as the iterator is not deleted and ReleasePinnedData() is not called.
|
||||
// If true, this means that the Slice returned by key() is valid as long as
|
||||
// PinnedIteratorsManager::ReleasePinnedIterators is not called and the
|
||||
// Iterator is not deleted.
|
||||
//
|
||||
// IsKeyPinned() is guaranteed to always return true if
|
||||
// - PinData() is called
|
||||
// - Iterator is created with ReadOptions::pin_data = true
|
||||
// - DB tables were created with BlockBasedTableOptions::use_delta_encoding
|
||||
// set to false.
|
||||
virtual bool IsKeyPinned() const { return false; }
|
||||
|
||||
+23
-85
@@ -15,95 +15,47 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// A internal wrapper class with an interface similar to Iterator that
|
||||
// caches the valid() and key() results for an underlying iterator.
|
||||
// A internal wrapper class with an interface similar to Iterator that caches
|
||||
// the valid(), key() and IsKeyPinned() results for an underlying iterator.
|
||||
// This can help avoid virtual function calls and also gives better
|
||||
// cache locality.
|
||||
class IteratorWrapper {
|
||||
public:
|
||||
IteratorWrapper() : iter_(nullptr), iters_pinned_(false), valid_(false) {}
|
||||
explicit IteratorWrapper(InternalIterator* _iter)
|
||||
: iter_(nullptr), iters_pinned_(false) {
|
||||
IteratorWrapper() : iter_(nullptr), valid_(false) {}
|
||||
explicit IteratorWrapper(InternalIterator* _iter) : iter_(nullptr) {
|
||||
Set(_iter);
|
||||
}
|
||||
~IteratorWrapper() {}
|
||||
InternalIterator* iter() const { return iter_; }
|
||||
|
||||
// Takes the ownership of "_iter" and will delete it when destroyed.
|
||||
// Next call to Set() will destroy "_iter" except if PinData() was called.
|
||||
void Set(InternalIterator* _iter) {
|
||||
if (iters_pinned_ && iter_) {
|
||||
// keep old iterator until ReleasePinnedData() is called
|
||||
pinned_iters_.insert(iter_);
|
||||
} else {
|
||||
delete iter_;
|
||||
}
|
||||
// Set the underlying Iterator to _iter and return
|
||||
// previous underlying Iterator.
|
||||
InternalIterator* Set(InternalIterator* _iter) {
|
||||
InternalIterator* old_iter = iter_;
|
||||
|
||||
iter_ = _iter;
|
||||
if (iter_ == nullptr) {
|
||||
valid_ = false;
|
||||
} else {
|
||||
Update();
|
||||
if (iters_pinned_) {
|
||||
// Pin new iterator
|
||||
Status s = iter_->PinData();
|
||||
assert(s.ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status PinData() {
|
||||
Status s;
|
||||
if (iters_pinned_) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (iter_) {
|
||||
s = iter_->PinData();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
iters_pinned_ = true;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status ReleasePinnedData() {
|
||||
Status s;
|
||||
if (!iters_pinned_) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (iter_) {
|
||||
s = iter_->ReleasePinnedData();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
iters_pinned_ = false;
|
||||
// No need to call ReleasePinnedData() for pinned_iters_
|
||||
// since we will delete them
|
||||
DeletePinnedIterators(false);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const {
|
||||
assert(iter_);
|
||||
return iters_pinned_ && iter_->IsKeyPinned();
|
||||
return old_iter;
|
||||
}
|
||||
|
||||
void DeleteIter(bool is_arena_mode) {
|
||||
if (iter_ && pinned_iters_.find(iter_) == pinned_iters_.end()) {
|
||||
DestroyIterator(iter_, is_arena_mode);
|
||||
if (iter_) {
|
||||
if (!is_arena_mode) {
|
||||
delete iter_;
|
||||
} else {
|
||||
iter_->~InternalIterator();
|
||||
}
|
||||
}
|
||||
DeletePinnedIterators(is_arena_mode);
|
||||
}
|
||||
|
||||
// Iterator interface methods
|
||||
bool Valid() const { return valid_; }
|
||||
Slice key() const { assert(Valid()); return key_; }
|
||||
bool IsKeyPinned() const { assert(Valid()); return is_key_pinned_; }
|
||||
Slice value() const { assert(Valid()); return iter_->value(); }
|
||||
// Methods below require iter() != nullptr
|
||||
Status status() const { assert(iter_); return iter_->status(); }
|
||||
@@ -112,39 +64,25 @@ class IteratorWrapper {
|
||||
void Seek(const Slice& k) { assert(iter_); iter_->Seek(k); Update(); }
|
||||
void SeekToFirst() { assert(iter_); iter_->SeekToFirst(); Update(); }
|
||||
void SeekToLast() { assert(iter_); iter_->SeekToLast(); Update(); }
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) {
|
||||
assert(iter_);
|
||||
iter_->SetPinnedItersMgr(pinned_iters_mgr);
|
||||
Update();
|
||||
}
|
||||
|
||||
private:
|
||||
void Update() {
|
||||
valid_ = iter_->Valid();
|
||||
if (valid_) {
|
||||
key_ = iter_->key();
|
||||
}
|
||||
}
|
||||
|
||||
void DeletePinnedIterators(bool is_arena_mode) {
|
||||
for (auto it : pinned_iters_) {
|
||||
DestroyIterator(it, is_arena_mode);
|
||||
}
|
||||
pinned_iters_.clear();
|
||||
}
|
||||
|
||||
inline void DestroyIterator(InternalIterator* it, bool is_arena_mode) {
|
||||
if (!is_arena_mode) {
|
||||
delete it;
|
||||
} else {
|
||||
it->~InternalIterator();
|
||||
is_key_pinned_ = iter_->IsKeyPinned();
|
||||
}
|
||||
}
|
||||
|
||||
InternalIterator* iter_;
|
||||
// If set to true, current and future iterators wont be deleted.
|
||||
bool iters_pinned_;
|
||||
// List of past iterators that are pinned and wont be deleted as long as
|
||||
// iters_pinned_ is true. When we are pinning iterators this set will contain
|
||||
// iterators of previous data blocks to keep them from being deleted.
|
||||
std::set<InternalIterator*> pinned_iters_;
|
||||
bool valid_;
|
||||
Slice key_;
|
||||
bool is_key_pinned_;
|
||||
};
|
||||
|
||||
class Arena;
|
||||
|
||||
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user