mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
115
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06ac8ba7f9 | ||
|
|
00b4ed2f90 | ||
|
|
2b7ee8e9c6 | ||
|
|
79f39ff931 | ||
|
|
b54712ed7d | ||
|
|
272d006037 | ||
|
|
51b25da6f8 | ||
|
|
8f2ef73d4b | ||
|
|
a964d457a9 | ||
|
|
0279d2711f | ||
|
|
20bc5166b3 | ||
|
|
cc0b5a4c14 | ||
|
|
145d48a02e | ||
|
|
b3f017fc31 | ||
|
|
3eb30d64d3 | ||
|
|
06c2935d83 | ||
|
|
b3621aa555 | ||
|
|
a8c6000d86 | ||
|
|
b490f3d753 | ||
|
|
eaf95c7d1a | ||
|
|
2ae91c6097 | ||
|
|
cb671ea1ca | ||
|
|
e8d332d97e | ||
|
|
ee50b8d499 | ||
|
|
3a35542f86 | ||
|
|
3881a678d5 | ||
|
|
4246888101 | ||
|
|
2e276973e4 | ||
|
|
8abd41a544 | ||
|
|
e91d1a21a6 | ||
|
|
6f62322fe4 | ||
|
|
6301dbe7a7 | ||
|
|
963af52f15 | ||
|
|
e70629e5f7 | ||
|
|
8599efabab | ||
|
|
93b80ca7ba | ||
|
|
ccf7676455 | ||
|
|
6fd0ed4993 | ||
|
|
ceeca7542d | ||
|
|
a6ce5c823b | ||
|
|
921cdd37e2 | ||
|
|
a9d168cfd7 | ||
|
|
43aee93d2b | ||
|
|
d300d10962 | ||
|
|
617f479266 | ||
|
|
5c6346c420 | ||
|
|
fb09ef05dc | ||
|
|
4fc216649d | ||
|
|
5fd152b7ad | ||
|
|
6c50fe1ec9 | ||
|
|
66cd07c6d9 | ||
|
|
d2e3822d67 | ||
|
|
093ff0b2ce | ||
|
|
e10553f2a6 | ||
|
|
442404558a | ||
|
|
217ce20021 | ||
|
|
8ad4b32c5d | ||
|
|
1df9b01680 | ||
|
|
712bc4b6a2 | ||
|
|
a8149aef1e | ||
|
|
66ed58083a | ||
|
|
098dce2d1a | ||
|
|
6c595f008a | ||
|
|
db02664f35 | ||
|
|
58918d4ccc | ||
|
|
488b1e6739 | ||
|
|
d66908091d | ||
|
|
402da454cb | ||
|
|
23eae14d24 | ||
|
|
a824727db4 | ||
|
|
85dbdf2586 | ||
|
|
5c30e6c088 | ||
|
|
674cf41732 | ||
|
|
dd7a4a8f03 | ||
|
|
a8851f2d05 | ||
|
|
2ccb794eb6 | ||
|
|
0d2c8e47e8 | ||
|
|
0772768d07 | ||
|
|
c15e85bdcb | ||
|
|
07a3f7f008 | ||
|
|
6dea7530b5 | ||
|
|
2d9efc9ab2 | ||
|
|
331e6199df | ||
|
|
37a635cfe6 | ||
|
|
8fc20ac468 | ||
|
|
65b60db9e1 | ||
|
|
00c4ab01b9 | ||
|
|
f5bc3b99d5 | ||
|
|
4028eba67b | ||
|
|
fd1da22111 | ||
|
|
d93812c9ae | ||
|
|
0a0151fb99 | ||
|
|
f6c2777d95 | ||
|
|
e171a219d5 | ||
|
|
afb97094ae | ||
|
|
e62fe50634 | ||
|
|
8bbd76edbf | ||
|
|
03dbd11ead | ||
|
|
48d8d076a3 | ||
|
|
17bef7d3a8 | ||
|
|
8d73137ae8 | ||
|
|
904a60ff63 | ||
|
|
8637bc1eea | ||
|
|
5f2f8cd97c | ||
|
|
9b3c9ef0e8 | ||
|
|
86f1ad7046 | ||
|
|
051696bf98 | ||
|
|
41535d0218 | ||
|
|
72ee067b90 | ||
|
|
741decfe37 | ||
|
|
f52db84650 | ||
|
|
69679e7375 | ||
|
|
d87c10c6ab | ||
|
|
eb367d45c0 | ||
|
|
b47a714051 |
No files matched your search
@@ -0,0 +1,56 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
win: circleci/windows@2.4.0
|
||||
|
||||
executors:
|
||||
windows-2xlarge:
|
||||
machine:
|
||||
image: 'windows-server-2019-vs2019:stable'
|
||||
resource_class: windows.2xlarge
|
||||
shell: bash.exe
|
||||
|
||||
jobs:
|
||||
build:
|
||||
executor: windows-2xlarge
|
||||
|
||||
environment:
|
||||
THIRDPARTY_HOME: C:/Users/circleci/thirdparty
|
||||
CMAKE_HOME: C:/Users/circleci/thirdparty/cmake-3.16.4-win64-x64
|
||||
CMAKE_BIN: C:/Users/circleci/thirdparty/cmake-3.16.4-win64-x64/bin/cmake.exe
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.7
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.7;C:/Users/circleci/thirdparty/snappy-1.1.7/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.7/build/Debug/snappy.lib
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "Install thirdparty dependencies"
|
||||
command: |
|
||||
mkdir ${THIRDPARTY_HOME}
|
||||
cd ${THIRDPARTY_HOME}
|
||||
echo "Installing CMake..."
|
||||
curl --fail --silent --show-error --output cmake-3.16.4-win64-x64.zip --location https://github.com/Kitware/CMake/releases/download/v3.16.4/cmake-3.16.4-win64-x64.zip
|
||||
unzip -q cmake-3.16.4-win64-x64.zip
|
||||
echo "Building Snappy dependency..."
|
||||
curl --fail --silent --show-error --output snappy-1.1.7.zip --location https://github.com/google/snappy/archive/1.1.7.zip
|
||||
unzip -q snappy-1.1.7.zip
|
||||
cd snappy-1.1.7
|
||||
mkdir build
|
||||
cd build
|
||||
${CMAKE_BIN} -G "${CMAKE_GENERATOR}" ..
|
||||
msbuild.exe Snappy.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
- run:
|
||||
name: "Build RocksDB"
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
${CMAKE_BIN} -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DJNI=1 ..
|
||||
cd ..
|
||||
msbuild.exe build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
- run:
|
||||
name: "Test RocksDB"
|
||||
shell: powershell.exe
|
||||
command: |
|
||||
build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test,db_test2,env_basic_test,env_test,db_merge_operand_test -Concurrency 16
|
||||
@@ -34,6 +34,7 @@ manifest_dump
|
||||
sst_dump
|
||||
blob_dump
|
||||
block_cache_trace_analyzer
|
||||
db_with_timestamp_basic_test
|
||||
tools/block_cache_analyzer/*.pyc
|
||||
column_aware_encoding_exp
|
||||
util/build_version.cc
|
||||
@@ -82,3 +83,5 @@ fbcode/
|
||||
fbcode
|
||||
buckifier/*.pyc
|
||||
buckifier/__pycache__
|
||||
|
||||
compile_commands.json
|
||||
+122
-16
@@ -3,12 +3,14 @@ language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
arch:
|
||||
- amd64
|
||||
- arm64
|
||||
- ppc64le
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
osx_image: xcode9.4
|
||||
jdk:
|
||||
- openjdk7
|
||||
cache:
|
||||
- ccache
|
||||
|
||||
@@ -52,22 +54,109 @@ env:
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: TEST_GROUP=1
|
||||
- os: osx
|
||||
env: TEST_GROUP=2
|
||||
- os: osx
|
||||
env: TEST_GROUP=3
|
||||
- os: osx
|
||||
env: TEST_GROUP=4
|
||||
- os: osx
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- os : osx
|
||||
- os: osx
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os : linux
|
||||
compiler: clang
|
||||
- os : osx
|
||||
- os: osx
|
||||
arch: ppc64le
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
- os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os: linux
|
||||
compiler: clang
|
||||
# Exclude most osx, arm64 and ppc64le tests for pull requests, but build in branches
|
||||
# NB: the cmake build is a partial java test
|
||||
- if: type = pull_request
|
||||
os: osx
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request
|
||||
os: osx
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request
|
||||
os: osx
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request
|
||||
os: osx
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request
|
||||
os : osx
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request
|
||||
os : osx
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request
|
||||
os : osx
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- if: type = pull_request
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
|
||||
install:
|
||||
- if [ "${TRAVIS_OS_NAME}" == osx ]; then
|
||||
@@ -83,10 +172,27 @@ install:
|
||||
- if [[ "${JOB_NAME}" == cmake* ]] && [ "${TRAVIS_OS_NAME}" == linux ]; then
|
||||
CMAKE_DIST_URL="https://rocksdb-deps.s3-us-west-2.amazonaws.com/cmake/cmake-3.14.5-Linux-$(uname -m).tar.bz2";
|
||||
TAR_OPT="--strip-components=1 -xj";
|
||||
if [ "aarch64" == "$(uname -m)" ]; then
|
||||
sudo apt-get install -y libuv1 librhash0;
|
||||
sudo apt-get upgrade -y libstdc++6;
|
||||
fi;
|
||||
mkdir cmake-dist && curl --silent --fail --show-error --location "${CMAKE_DIST_URL}" | tar -C cmake-dist ${TAR_OPT} && export PATH=$PWD/cmake-dist/bin:$PATH;
|
||||
fi
|
||||
- if [[ "${JOB_NAME}" == java_test ]]; then
|
||||
java -version && echo "JAVA_HOME=${JAVA_HOME}";
|
||||
- |
|
||||
if [[ "${JOB_NAME}" == java_test || "${JOB_NAME}" == cmake* ]]; then
|
||||
# Ensure JDK 8
|
||||
if [ "${TRAVIS_OS_NAME}" == osx ]; then
|
||||
brew tap AdoptOpenJDK/openjdk
|
||||
brew cask install adoptopenjdk8
|
||||
export JAVA_HOME=$(/usr/libexec/java_home)
|
||||
else
|
||||
sudo apt-get install -y openjdk-8-jdk
|
||||
export PATH=/usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)/bin:$PATH
|
||||
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)
|
||||
fi
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
fi
|
||||
|
||||
before_script:
|
||||
|
||||
+42
-9
@@ -203,16 +203,25 @@ else()
|
||||
endif()
|
||||
|
||||
include(CheckCCompilerFlag)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
|
||||
CHECK_C_COMPILER_FLAG("-mcpu=power9" HAS_POWER9)
|
||||
if(HAS_POWER9)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power9 -mtune=power9")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power9 -mtune=power9")
|
||||
else()
|
||||
CHECK_C_COMPILER_FLAG("-mcpu=power8" HAS_POWER8)
|
||||
if(HAS_POWER8)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power8 -mtune=power8")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power8 -mtune=power8")
|
||||
endif(HAS_POWER8)
|
||||
endif(HAS_POWER9)
|
||||
CHECK_C_COMPILER_FLAG("-maltivec" HAS_ALTIVEC)
|
||||
if(HAS_ALTIVEC)
|
||||
message(STATUS " HAS_ALTIVEC yes")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maltivec")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maltivec")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power8")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power8")
|
||||
endif(HAS_ALTIVEC)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
|
||||
CHECK_C_COMPILER_FLAG("-march=armv8-a+crc+crypto" HAS_ARMV8_CRC)
|
||||
@@ -235,7 +244,7 @@ else()
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
else()
|
||||
if(NOT HAVE_POWER8 AND NOT HAS_ARMV8_CRC)
|
||||
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64" AND NOT HAS_ARMV8_CRC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
||||
endif()
|
||||
endif()
|
||||
@@ -481,6 +490,11 @@ if(HAVE_SCHED_GETCPU)
|
||||
add_definitions(-DROCKSDB_SCHED_GETCPU_PRESENT)
|
||||
endif()
|
||||
|
||||
check_cxx_symbol_exists(getauxval auvx.h HAVE_AUXV_GETAUXVAL)
|
||||
if(HAVE_AUXV_GETAUXVAL)
|
||||
add_definitions(-DROCKSDB_AUXV_GETAUXVAL_PRESENT)
|
||||
endif()
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third-party/gtest-1.8.1/fused-src)
|
||||
@@ -496,6 +510,9 @@ set(SOURCES
|
||||
cache/lru_cache.cc
|
||||
cache/sharded_cache.cc
|
||||
db/arena_wrapped_db_iter.cc
|
||||
db/blob/blob_file_addition.cc
|
||||
db/blob/blob_file_garbage.cc
|
||||
db/blob/blob_file_meta.cc
|
||||
db/builder.cc
|
||||
db/c.cc
|
||||
db/column_family.cc
|
||||
@@ -549,6 +566,7 @@ set(SOURCES
|
||||
db/trim_history_scheduler.cc
|
||||
db/version_builder.cc
|
||||
db/version_edit.cc
|
||||
db/version_edit_handler.cc
|
||||
db/version_set.cc
|
||||
db/wal_manager.cc
|
||||
db/write_batch.cc
|
||||
@@ -604,12 +622,15 @@ set(SOURCES
|
||||
options/options_sanity_check.cc
|
||||
port/stack_trace.cc
|
||||
table/adaptive/adaptive_table_factory.cc
|
||||
table/block_based/binary_search_index_reader.cc
|
||||
table/block_based/block.cc
|
||||
table/block_based/block_based_filter_block.cc
|
||||
table/block_based/block_based_table_builder.cc
|
||||
table/block_based/block_based_table_factory.cc
|
||||
table/block_based/block_based_table_iterator.cc
|
||||
table/block_based/block_based_table_reader.cc
|
||||
table/block_based/block_builder.cc
|
||||
table/block_based/block_prefetcher.cc
|
||||
table/block_based/block_prefix_index.cc
|
||||
table/block_based/data_block_hash_index.cc
|
||||
table/block_based/data_block_footer.cc
|
||||
@@ -617,9 +638,14 @@ set(SOURCES
|
||||
table/block_based/filter_policy.cc
|
||||
table/block_based/flush_block_policy.cc
|
||||
table/block_based/full_filter_block.cc
|
||||
table/block_based/hash_index_reader.cc
|
||||
table/block_based/index_builder.cc
|
||||
table/block_based/index_reader_common.cc
|
||||
table/block_based/parsed_full_filter_block.cc
|
||||
table/block_based/partitioned_filter_block.cc
|
||||
table/block_based/partitioned_index_iterator.cc
|
||||
table/block_based/partitioned_index_reader.cc
|
||||
table/block_based/reader_common.cc
|
||||
table/block_based/uncompression_dict_reader.cc
|
||||
table/block_fetcher.cc
|
||||
table/cuckoo/cuckoo_table_builder.cc
|
||||
@@ -734,11 +760,11 @@ if(HAVE_SSE42 AND NOT MSVC)
|
||||
PROPERTIES COMPILE_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
|
||||
if(HAVE_POWER8)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
|
||||
list(APPEND SOURCES
|
||||
util/crc32c_ppc.c
|
||||
util/crc32c_ppc_asm.S)
|
||||
endif(HAVE_POWER8)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
|
||||
|
||||
if(HAS_ARMV8_CRC)
|
||||
list(APPEND SOURCES
|
||||
@@ -919,6 +945,9 @@ if(WITH_TESTS)
|
||||
set(TESTS
|
||||
cache/cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
db/blob/blob_file_addition_test.cc
|
||||
db/blob/blob_file_garbage_test.cc
|
||||
db/blob/db_blob_index_test.cc
|
||||
db/column_family_test.cc
|
||||
db/compact_files_test.cc
|
||||
db/compaction/compaction_job_stats_test.cc
|
||||
@@ -929,7 +958,7 @@ if(WITH_TESTS)
|
||||
db/corruption_test.cc
|
||||
db/cuckoo_table_db_test.cc
|
||||
db/db_basic_test.cc
|
||||
db/db_blob_index_test.cc
|
||||
db/db_with_timestamp_basic_test.cc
|
||||
db/db_block_cache_test.cc
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_compaction_filter_test.cc
|
||||
@@ -955,12 +984,13 @@ if(WITH_TESTS)
|
||||
db/db_tailing_iter_test.cc
|
||||
db/db_test.cc
|
||||
db/db_test2.cc
|
||||
db/db_logical_block_size_cache_test.cc
|
||||
db/db_universal_compaction_test.cc
|
||||
db/db_wal_test.cc
|
||||
db/db_write_test.cc
|
||||
db/dbformat_test.cc
|
||||
db/deletefile_test.cc
|
||||
db/error_handler_test.cc
|
||||
db/error_handler_fs_test.cc
|
||||
db/obsolete_files_test.cc
|
||||
db/external_sst_file_basic_test.cc
|
||||
db/external_sst_file_test.cc
|
||||
@@ -991,8 +1021,10 @@ if(WITH_TESTS)
|
||||
db/write_controller_test.cc
|
||||
env/env_basic_test.cc
|
||||
env/env_test.cc
|
||||
env/io_posix_test.cc
|
||||
env/mock_env_test.cc
|
||||
file/delete_scheduler_test.cc
|
||||
file/random_access_file_reader_test.cc
|
||||
logging/auto_roll_logger_test.cc
|
||||
logging/env_logger_test.cc
|
||||
logging/event_logger_test.cc
|
||||
@@ -1077,6 +1109,7 @@ if(WITH_TESTS)
|
||||
monitoring/thread_status_updater_debug.cc
|
||||
table/mock_table.cc
|
||||
test_util/fault_injection_test_env.cc
|
||||
test_util/fault_injection_test_fs.cc
|
||||
utilities/cassandra/test_utils.cc
|
||||
)
|
||||
enable_testing()
|
||||
|
||||
+37
-4
@@ -1,11 +1,44 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### Bug Fixes
|
||||
* Upgraded version of bzip library (1.0.6 -> 1.0.8) used with RocksJava to address potential vulnerabilities if an attacker can manipulate compressed data saved and loaded by RocksDB (not normal). See issue #6703.
|
||||
|
||||
## 6.8.1 (03/30/2020)
|
||||
### Public API Change
|
||||
* Add NewFileChecksumGenCrc32cFactory to the file checksum public API, such that the builtin Crc32c based file checksum generator factory can be used by applications.
|
||||
|
||||
### New Features
|
||||
* Log CompactOnDeletionCollectorFactory window_size and deletion_trigger parameters in the info log file for troubleshooting purposes.
|
||||
* Report kFilesMarkedForCompaction for delete triggered compactions in case of Universal and Levelled Compactions.
|
||||
|
||||
## 6.9.1 (04/09/2020)
|
||||
### Bug Fixes
|
||||
* Fix wrong result being read from ingested file. May happen when a key in the file happen to be prefix of another key also in the file. The issue can further cause more data corruption. The issue exists with rocksdb >= 5.0.0 since DB::IngestExternalFile() was introduced.
|
||||
* Fix a bug when making options.bottommost_compression, options.compression_opts and options.bottommost_compression_opts dynamically changeable: the modified values are not written to option files or returned back to users when being queried.
|
||||
|
||||
## 6.9.0 (03/29/2020)
|
||||
### Behavior changes
|
||||
* Since RocksDB 6.8.0, ttl-based FIFO compaction can drop a file whose oldest key becomes older than options.ttl while others have not. This fix reverts this and makes ttl-based FIFO compaction use the file's flush time as the criterion. This fix also requires that max_open_files = -1 and compaction_options_fifo.allow_compaction = false to function properly.
|
||||
* Since RocksDB 6.8, ttl-based FIFO compaction can drop a file whose oldest key becomes older than options.ttl while others have not. This fix reverts this and makes ttl-based FIFO compaction use the file's flush time as the criterion. This fix also requires that max_open_files = -1 and compaction_options_fifo.allow_compaction = false to function properly.
|
||||
|
||||
### Public API Change
|
||||
* Fix spelling so that API now has correctly spelled transaction state name `COMMITTED`, while the old misspelled `COMMITED` is still available as an alias.
|
||||
* Updated default format_version in BlockBasedTableOptions from 2 to 4. SST files generated with the new default can be read by RocksDB versions 5.16 and newer, and use more efficient encoding of keys in index blocks.
|
||||
* A new parameter `CreateBackupOptions` is added to both `BackupEngine::CreateNewBackup` and `BackupEngine::CreateNewBackupWithMetadata`, you can decrease CPU priority of `BackupEngine`'s background threads by setting `decrease_background_thread_cpu_priority` and `background_thread_cpu_priority` in `CreateBackupOptions`.
|
||||
* Updated the public API of SST file checksum. Introduce the FileChecksumGenFactory to create the FileChecksumGenerator for each SST file, such that the FileChecksumGenerator is not shared and it can be more general for checksum implementations. Changed the FileChecksumGenerator interface from Value, Extend, and GetChecksum to Update, Finalize, and GetChecksum. Finalize should be only called once after all data is processed to generate the final checksum. Temproal data should be maintained by the FileChecksumGenerator object itself and finally it can return the checksum string.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where range tombstone blocks in ingested files were cached incorrectly during ingestion. If range tombstones were read from those incorrectly cached blocks, the keys they covered would be exposed.
|
||||
* Fix a data race that might cause crash when calling DB::GetCreationTimeOfOldestFile() by a small chance. The bug was introduced in 6.6 Release.
|
||||
* Fix a bug where a boolean value optimize_filters_for_hits was for max threads when calling load table handles after a flush or compaction. The value is correct to 1. The bug should not cause user visible problems.
|
||||
* Fix a bug which might crash the service when write buffer manager fails to insert the dummy handle to the block cache.
|
||||
|
||||
### Performance Improvements
|
||||
* In CompactRange, for levels starting from 0, if the level does not have any file with any key falling in the specified range, the level is skipped. So instead of always compacting from level 0, the compaction starts from the first level with keys in the specified range until the last such level.
|
||||
* Reduced memory copy when reading sst footer and blobdb in direct IO mode.
|
||||
* When restarting a database with large numbers of sst files, large amount of CPU time is spent on getting logical block size of the sst files, which slows down the starting progress, this inefficiency is optimized away with an internal cache for the logical block sizes.
|
||||
|
||||
### New Features
|
||||
* Basic support for user timestamp in iterator. Seek/SeekToFirst/Next and lower/upper bounds are supported. Reverse iteration is not supported. Merge is not considered.
|
||||
* When file lock failure when the lock is held by the current process, return acquiring time and thread ID in the error message.
|
||||
* Added a new option, best_efforts_recovery (default: false), to allow database to open in a db dir with missing table files. During best efforts recovery, missing table files are ignored, and database recovers to the most recent state without missing table file. Cross-column-family consistency is not guaranteed even if WAL is enabled.
|
||||
* options.bottommost_compression, options.compression_opts and options.bottommost_compression_opts are now dynamically changeable.
|
||||
|
||||
## 6.8.0 (02/24/2020)
|
||||
### Java API Changes
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
BASH_EXISTS := $(shell which bash)
|
||||
SHELL := $(shell which bash)
|
||||
PYTHON?=$(shell which python)
|
||||
|
||||
CLEAN_FILES = # deliberately empty, so we can append below.
|
||||
CFLAGS += ${EXTRA_CFLAGS}
|
||||
@@ -207,6 +208,7 @@ AM_LINK = $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; export PORTABLE="$(PORTABLE)"; "$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
export JAVAC_ARGS
|
||||
CLEAN_FILES += make_config.mk
|
||||
|
||||
missing_make_config_paths := $(shell \
|
||||
@@ -216,7 +218,7 @@ missing_make_config_paths := $(shell \
|
||||
done | sort | uniq)
|
||||
|
||||
$(foreach path, $(missing_make_config_paths), \
|
||||
$(warning Warning: $(path) dont exist))
|
||||
$(warning Warning: $(path) does not exist))
|
||||
|
||||
ifeq ($(PLATFORM), OS_AIX)
|
||||
# no debug info
|
||||
@@ -445,6 +447,7 @@ EXPOBJECTS = $(LIBOBJECTS) $(TESTUTIL)
|
||||
|
||||
TESTS = \
|
||||
db_basic_test \
|
||||
db_with_timestamp_basic_test \
|
||||
db_encryption_test \
|
||||
db_test2 \
|
||||
external_sst_file_basic_test \
|
||||
@@ -459,6 +462,7 @@ TESTS = \
|
||||
env_basic_test \
|
||||
env_test \
|
||||
env_logger_test \
|
||||
io_posix_test \
|
||||
hash_test \
|
||||
random_test \
|
||||
thread_local_test \
|
||||
@@ -468,6 +472,7 @@ TESTS = \
|
||||
db_wal_test \
|
||||
db_block_cache_test \
|
||||
db_test \
|
||||
db_logical_block_size_cache_test \
|
||||
db_blob_index_test \
|
||||
db_iter_test \
|
||||
db_iter_stress_test \
|
||||
@@ -492,7 +497,7 @@ TESTS = \
|
||||
db_table_properties_test \
|
||||
db_statistics_test \
|
||||
db_write_test \
|
||||
error_handler_test \
|
||||
error_handler_fs_test \
|
||||
autovector_test \
|
||||
blob_db_test \
|
||||
cleanable_test \
|
||||
@@ -509,6 +514,7 @@ TESTS = \
|
||||
fault_injection_test \
|
||||
filelock_test \
|
||||
filename_test \
|
||||
random_access_file_reader_test \
|
||||
file_reader_writer_test \
|
||||
block_based_filter_block_test \
|
||||
full_filter_block_test \
|
||||
@@ -597,6 +603,8 @@ TESTS = \
|
||||
block_cache_tracer_test \
|
||||
block_cache_trace_analyzer_test \
|
||||
defer_test \
|
||||
blob_file_addition_test \
|
||||
blob_file_garbage_test \
|
||||
|
||||
ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
TESTS += folly_synchronization_distributed_mutex_test
|
||||
@@ -941,9 +949,9 @@ check: all
|
||||
fi
|
||||
rm -rf $(TMPD)
|
||||
ifneq ($(PLATFORM), OS_AIX)
|
||||
python tools/check_all_python.py
|
||||
$(PYTHON) tools/check_all_python.py
|
||||
ifeq ($(filter -DROCKSDB_LITE,$(OPT)),)
|
||||
python tools/ldb_test.py
|
||||
$(PYTHON) tools/ldb_test.py
|
||||
sh tools/rocksdb_dump_test.sh
|
||||
endif
|
||||
endif
|
||||
@@ -954,7 +962,7 @@ check_some: $(SUBSET)
|
||||
|
||||
.PHONY: ldb_tests
|
||||
ldb_tests: ldb
|
||||
python tools/ldb_test.py
|
||||
$(PYTHON) tools/ldb_test.py
|
||||
|
||||
crash_test: whitebox_crash_test blackbox_crash_test
|
||||
|
||||
@@ -963,31 +971,31 @@ crash_test_with_atomic_flush: whitebox_crash_test_with_atomic_flush blackbox_cra
|
||||
crash_test_with_txn: whitebox_crash_test_with_txn blackbox_crash_test_with_txn
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
python -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_atomic_flush: db_stress
|
||||
python -u tools/db_crashtest.py --cf_consistency blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py --cf_consistency blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_txn: db_stress
|
||||
python -u tools/db_crashtest.py --txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py --txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
|
||||
whitebox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py --simple whitebox --random_kill_odd \
|
||||
$(PYTHON) -u tools/db_crashtest.py --simple whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
python -u tools/db_crashtest.py whitebox --random_kill_odd \
|
||||
$(PYTHON) -u tools/db_crashtest.py whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_atomic_flush: db_stress
|
||||
python -u tools/db_crashtest.py --cf_consistency whitebox --random_kill_odd \
|
||||
$(PYTHON) -u tools/db_crashtest.py --cf_consistency whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_txn: db_stress
|
||||
python -u tools/db_crashtest.py --txn whitebox --random_kill_odd \
|
||||
$(PYTHON) -u tools/db_crashtest.py --txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
asan_check:
|
||||
@@ -1302,6 +1310,9 @@ slice_transform_test: util/slice_transform_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_basic_test: db/db_basic_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_with_timestamp_basic_test: db/db_with_timestamp_basic_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_encryption_test: db/db_encryption_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1311,7 +1322,10 @@ db_test: db/db_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_test2: db/db_test2.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_index_test: db/db_blob_index_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_logical_block_size_cache_test: db/db_logical_block_size_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_index_test: db/blob/db_blob_index_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_block_cache_test: db/db_block_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
@@ -1365,7 +1379,7 @@ db_statistics_test: db/db_statistics_test.o db/db_test_util.o $(LIBOBJECTS) $(TE
|
||||
db_write_test: db/db_write_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
error_handler_test: db/error_handler_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
error_handler_fs_test: db/error_handler_fs_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
external_sst_file_basic_test: db/external_sst_file_basic_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
@@ -1478,6 +1492,9 @@ env_basic_test: env/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
env_test: env/env_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
io_posix_test: env/io_posix_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
fault_injection_test: db/fault_injection_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1490,6 +1507,9 @@ delete_scheduler_test: file/delete_scheduler_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
filename_test: db/filename_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
random_access_file_reader_test: file/random_access_file_reader_test.o $(LIBOBJECTS) $(TESTHARNESS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
file_reader_writer_test: util/file_reader_writer_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1718,6 +1738,12 @@ block_cache_trace_analyzer_test: tools/block_cache_analyzer/block_cache_trace_an
|
||||
defer_test: util/defer_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_file_addition_test: db/blob/blob_file_addition_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_file_garbage_test: db/blob/blob_file_garbage_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
@@ -1797,9 +1823,9 @@ SHA256_CMD = sha256sum
|
||||
ZLIB_VER ?= 1.2.11
|
||||
ZLIB_SHA256 ?= c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
BZIP2_DOWNLOAD_BASE ?= https://sourceware.org/pub/bzip2
|
||||
BZIP2_VER ?= 1.0.6
|
||||
BZIP2_SHA256 ?= a2848f34fcd5d6cf47def00461fcb528a0484d8edef8208d6d2e2909dc61d9cd
|
||||
BZIP2_DOWNLOAD_BASE ?= https://downloads.sourceforge.net/project/bzip2
|
||||
SNAPPY_VER ?= 1.1.8
|
||||
SNAPPY_SHA256 ?= 16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
@@ -2068,7 +2094,7 @@ jtest_run:
|
||||
|
||||
jtest: rocksdbjava
|
||||
cd java;$(MAKE) sample;$(MAKE) test;
|
||||
python tools/check_all_python.py # TODO peterd: find a better place for this check in CI targets
|
||||
$(PYTHON) tools/check_all_python.py # TODO peterd: find a better place for this check in CI targets
|
||||
|
||||
jdb_bench:
|
||||
cd java;$(MAKE) db_bench;
|
||||
|
||||
@@ -116,6 +116,9 @@ cpp_library(
|
||||
"cache/lru_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/builder.cc",
|
||||
"db/c.cc",
|
||||
"db/column_family.cc",
|
||||
@@ -169,6 +172,7 @@ cpp_library(
|
||||
"db/trim_history_scheduler.cc",
|
||||
"db/version_builder.cc",
|
||||
"db/version_edit.cc",
|
||||
"db/version_edit_handler.cc",
|
||||
"db/version_set.cc",
|
||||
"db/wal_manager.cc",
|
||||
"db/write_batch.cc",
|
||||
@@ -229,12 +233,15 @@ cpp_library(
|
||||
"port/port_posix.cc",
|
||||
"port/stack_trace.cc",
|
||||
"table/adaptive/adaptive_table_factory.cc",
|
||||
"table/block_based/binary_search_index_reader.cc",
|
||||
"table/block_based/block.cc",
|
||||
"table/block_based/block_based_filter_block.cc",
|
||||
"table/block_based/block_based_table_builder.cc",
|
||||
"table/block_based/block_based_table_factory.cc",
|
||||
"table/block_based/block_based_table_iterator.cc",
|
||||
"table/block_based/block_based_table_reader.cc",
|
||||
"table/block_based/block_builder.cc",
|
||||
"table/block_based/block_prefetcher.cc",
|
||||
"table/block_based/block_prefix_index.cc",
|
||||
"table/block_based/data_block_footer.cc",
|
||||
"table/block_based/data_block_hash_index.cc",
|
||||
@@ -242,9 +249,14 @@ cpp_library(
|
||||
"table/block_based/filter_policy.cc",
|
||||
"table/block_based/flush_block_policy.cc",
|
||||
"table/block_based/full_filter_block.cc",
|
||||
"table/block_based/hash_index_reader.cc",
|
||||
"table/block_based/index_builder.cc",
|
||||
"table/block_based/index_reader_common.cc",
|
||||
"table/block_based/parsed_full_filter_block.cc",
|
||||
"table/block_based/partitioned_filter_block.cc",
|
||||
"table/block_based/partitioned_index_iterator.cc",
|
||||
"table/block_based/partitioned_index_reader.cc",
|
||||
"table/block_based/reader_common.cc",
|
||||
"table/block_based/uncompression_dict_reader.cc",
|
||||
"table/block_fetcher.cc",
|
||||
"table/cuckoo/cuckoo_table_builder.cc",
|
||||
@@ -367,6 +379,7 @@ cpp_library(
|
||||
"db/db_test_util.cc",
|
||||
"table/mock_table.cc",
|
||||
"test_util/fault_injection_test_env.cc",
|
||||
"test_util/fault_injection_test_fs.cc",
|
||||
"test_util/testharness.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
@@ -477,6 +490,20 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_file_addition_test",
|
||||
"db/blob/blob_file_addition_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_file_garbage_test",
|
||||
"db/blob/blob_file_garbage_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"block_based_filter_block_test",
|
||||
"table/block_based/block_based_filter_block_test.cc",
|
||||
@@ -689,7 +716,7 @@ ROCKS_TESTS = [
|
||||
],
|
||||
[
|
||||
"db_blob_index_test",
|
||||
"db/db_blob_index_test.cc",
|
||||
"db/blob/db_blob_index_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
@@ -785,6 +812,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"db_logical_block_size_cache_test",
|
||||
"db/db_logical_block_size_cache_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"db_memtable_test",
|
||||
"db/db_memtable_test.cc",
|
||||
@@ -890,6 +924,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"db_with_timestamp_basic_test",
|
||||
"db/db_with_timestamp_basic_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"db_write_test",
|
||||
"db/db_write_test.cc",
|
||||
@@ -961,8 +1002,8 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
],
|
||||
[
|
||||
"error_handler_test",
|
||||
"db/error_handler_test.cc",
|
||||
"error_handler_fs_test",
|
||||
"db/error_handler_fs_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
@@ -1079,6 +1120,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"io_posix_test",
|
||||
"env/io_posix_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"iostats_context_test",
|
||||
"monitoring/iostats_context_test.cc",
|
||||
@@ -1254,6 +1302,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"random_access_file_reader_test",
|
||||
"file/random_access_file_reader_test.cc",
|
||||
"serial",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"random_test",
|
||||
"util/random_test.cc",
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ build:
|
||||
test:
|
||||
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test2,db_test,env_basic_test,env_test,db_merge_operand_test -Concurrency 8
|
||||
- ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_with_timestamp_basic_test,db_test2,db_test,env_basic_test,env_test,db_merge_operand_test -Concurrency 8
|
||||
|
||||
on_failure:
|
||||
- cmd: 7z a build-failed.zip %APPVEYOR_BUILD_FOLDER%\build\ && appveyor PushArtifact build-failed.zip
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
# PLATFORM_LDFLAGS Linker flags
|
||||
# JAVA_LDFLAGS Linker flags for RocksDBJava
|
||||
# JAVA_STATIC_LDFLAGS Linker flags for RocksDBJava static build
|
||||
# JAVAC_ARGS Arguments for javac
|
||||
# PLATFORM_SHARED_EXT Extension for shared libraries
|
||||
# PLATFORM_SHARED_LDFLAGS Flags for building shared library
|
||||
# PLATFORM_SHARED_CFLAGS Flags for compiling objects for shared library
|
||||
@@ -239,6 +240,7 @@ esac
|
||||
PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS ${CXXFLAGS}"
|
||||
JAVA_LDFLAGS="$PLATFORM_LDFLAGS"
|
||||
JAVA_STATIC_LDFLAGS="$PLATFORM_LDFLAGS"
|
||||
JAVAC_ARGS="-source 7"
|
||||
|
||||
if [ "$CROSS_COMPILE" = "true" -o "$FBCODE_BUILD" = "true" ]; then
|
||||
# Cross-compiling; do not try any compilation tests.
|
||||
@@ -508,6 +510,20 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_AUXV_GETAUXVAL; then
|
||||
# Test whether getauxval is supported
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sys/auxv.h>
|
||||
int main() {
|
||||
uint64_t auxv = getauxval(AT_HWCAP);
|
||||
(void)auxv;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_AUXV_GETAUXVAL_PRESENT"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ALIGNED_NEW; then
|
||||
# Test whether c++17 aligned-new is supported
|
||||
$CXX $PLATFORM_CXXFLAGS -faligned-new -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
@@ -696,6 +712,7 @@ echo "PLATFORM=$PLATFORM" >> "$OUTPUT"
|
||||
echo "PLATFORM_LDFLAGS=$PLATFORM_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_LDFLAGS=$JAVA_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_STATIC_LDFLAGS=$JAVA_STATIC_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVAC_ARGS=$JAVAC_ARGS" >> "$OUTPUT"
|
||||
echo "VALGRIND_VER=$VALGRIND_VER" >> "$OUTPUT"
|
||||
echo "PLATFORM_CCFLAGS=$PLATFORM_CCFLAGS" >> "$OUTPUT"
|
||||
echo "PLATFORM_CXXFLAGS=$PLATFORM_CXXFLAGS" >> "$OUTPUT"
|
||||
|
||||
@@ -5801,7 +5801,7 @@ sub workdir {
|
||||
. "-" . $self->seq();
|
||||
} else {
|
||||
$workdir = $opt::workdir;
|
||||
# Rsync treats /./ special. We dont want that
|
||||
# Rsync treats /./ special. We don't want that
|
||||
$workdir =~ s:/\./:/:g; # Remove /./
|
||||
$workdir =~ s:/+$::; # Remove ending / if any
|
||||
$workdir =~ s:^\./::g; # Remove starting ./ if any
|
||||
|
||||
@@ -17,16 +17,19 @@ if [ -d /mnt/gvfs/third-party -a -z "$CXX" ]; then
|
||||
else
|
||||
GCOV=$(which gcov)
|
||||
fi
|
||||
echo -e "Using $GCOV"
|
||||
|
||||
COVERAGE_DIR="$PWD/COVERAGE_REPORT"
|
||||
mkdir -p $COVERAGE_DIR
|
||||
|
||||
# Find all gcno files to generate the coverage report
|
||||
|
||||
PYTHON=${1:-`which python`}
|
||||
echo -e "Using $PYTHON"
|
||||
GCNO_FILES=`find $ROOT -name "*.gcno"`
|
||||
$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null |
|
||||
# Parse the raw gcov report to more human readable form.
|
||||
python $ROOT/coverage/parse_gcov_output.py |
|
||||
$PYTHON $ROOT/coverage/parse_gcov_output.py |
|
||||
# Write the output to both stdout and report file.
|
||||
tee $COVERAGE_DIR/coverage_report_all.txt &&
|
||||
echo -e "Generated coverage report for all files: $COVERAGE_DIR/coverage_report_all.txt\n"
|
||||
@@ -41,7 +44,7 @@ RECENT_REPORT=$COVERAGE_DIR/coverage_report_recent.txt
|
||||
|
||||
echo -e "Recently updated files: $LATEST_FILES\n" > $RECENT_REPORT
|
||||
$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null |
|
||||
python $ROOT/coverage/parse_gcov_output.py -interested-files $LATEST_FILES |
|
||||
$PYTHON $ROOT/coverage/parse_gcov_output.py -interested-files $LATEST_FILES |
|
||||
tee -a $RECENT_REPORT &&
|
||||
echo -e "Generated coverage report for recently updated files: $RECENT_REPORT\n"
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import optparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
# the gcov report follows certain pattern. Each file will have two lines
|
||||
# of report, from which we can extract the file name, total lines and coverage
|
||||
# percentage.
|
||||
@@ -48,7 +50,7 @@ def parse_gcov_report(gcov_input):
|
||||
def get_option_parser():
|
||||
usage = "Parse the gcov output and generate more human-readable code " +\
|
||||
"coverage report."
|
||||
parser = OptionParser(usage)
|
||||
parser = optparse.OptionParser(usage)
|
||||
|
||||
parser.add_option(
|
||||
"--interested-files", "-i",
|
||||
@@ -73,8 +75,8 @@ def display_file_coverage(per_file_coverage, total_coverage):
|
||||
header_template = \
|
||||
"%" + str(max_file_name_length) + "s\t%s\t%s"
|
||||
separator = "-" * (max_file_name_length + 10 + 20)
|
||||
print header_template % ("Filename", "Coverage", "Lines") # noqa: E999 T25377293 Grandfathered in
|
||||
print separator
|
||||
print(header_template % ("Filename", "Coverage", "Lines")) # noqa: E999 T25377293 Grandfathered in
|
||||
print(separator)
|
||||
|
||||
# -- Print body
|
||||
# template for printing coverage report for each file.
|
||||
@@ -82,12 +84,12 @@ def display_file_coverage(per_file_coverage, total_coverage):
|
||||
|
||||
for fname, coverage_info in per_file_coverage.items():
|
||||
coverage, lines = coverage_info
|
||||
print record_template % (fname, coverage, lines)
|
||||
print(record_template % (fname, coverage, lines))
|
||||
|
||||
# -- Print footer
|
||||
if total_coverage:
|
||||
print separator
|
||||
print record_template % ("Total", total_coverage[0], total_coverage[1])
|
||||
print(separator)
|
||||
print(record_template % ("Total", total_coverage[0], total_coverage[1]))
|
||||
|
||||
def report_coverage():
|
||||
parser = get_option_parser()
|
||||
@@ -111,7 +113,7 @@ def report_coverage():
|
||||
total_coverage = None
|
||||
|
||||
if not len(per_file_coverage):
|
||||
print >> sys.stderr, "Cannot find coverage info for the given files."
|
||||
print("Cannot find coverage info for the given files.", file=sys.stderr)
|
||||
return
|
||||
display_file_coverage(per_file_coverage, total_coverage)
|
||||
|
||||
|
||||
@@ -51,6 +51,8 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
bool Valid() const override { return db_iter_->Valid(); }
|
||||
void SeekToFirst() override { db_iter_->SeekToFirst(); }
|
||||
void SeekToLast() override { db_iter_->SeekToLast(); }
|
||||
// 'target' does not contain timestamp, even if user timestamp feature is
|
||||
// enabled.
|
||||
void Seek(const Slice& target) override { db_iter_->Seek(target); }
|
||||
void SeekForPrev(const Slice& target) override {
|
||||
db_iter_->SeekForPrev(target);
|
||||
@@ -60,6 +62,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
Slice key() const override { return db_iter_->key(); }
|
||||
Slice value() const override { return db_iter_->value(); }
|
||||
Status status() const override { return db_iter_->status(); }
|
||||
Slice timestamp() const override { return db_iter_->timestamp(); }
|
||||
bool IsBlob() const { return db_iter_->IsBlob(); }
|
||||
|
||||
Status GetProperty(std::string prop_name, std::string* prop) override;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
constexpr uint64_t kInvalidBlobFileNumber = 0;
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "logging/event_logger.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Tags for custom fields. Note that these get persisted in the manifest,
|
||||
// so existing tags should not be modified.
|
||||
enum BlobFileAddition::CustomFieldTags : uint32_t {
|
||||
kEndMarker,
|
||||
|
||||
// Add forward compatible fields here
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
kForwardIncompatibleMask = 1 << 6,
|
||||
|
||||
// Add forward incompatible fields here
|
||||
};
|
||||
|
||||
void BlobFileAddition::EncodeTo(std::string* output) const {
|
||||
PutVarint64(output, blob_file_number_);
|
||||
PutVarint64(output, total_blob_count_);
|
||||
PutVarint64(output, total_blob_bytes_);
|
||||
PutLengthPrefixedSlice(output, checksum_method_);
|
||||
PutLengthPrefixedSlice(output, checksum_value_);
|
||||
|
||||
// Encode any custom fields here. The format to use is a Varint32 tag (see
|
||||
// CustomFieldTags above) followed by a length prefixed slice. Unknown custom
|
||||
// fields will be ignored during decoding unless they're in the forward
|
||||
// incompatible range.
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("BlobFileAddition::EncodeTo::CustomFields", output);
|
||||
|
||||
PutVarint32(output, kEndMarker);
|
||||
}
|
||||
|
||||
Status BlobFileAddition::DecodeFrom(Slice* input) {
|
||||
constexpr char class_name[] = "BlobFileAddition";
|
||||
|
||||
if (!GetVarint64(input, &blob_file_number_)) {
|
||||
return Status::Corruption(class_name, "Error decoding blob file number");
|
||||
}
|
||||
|
||||
if (!GetVarint64(input, &total_blob_count_)) {
|
||||
return Status::Corruption(class_name, "Error decoding total blob count");
|
||||
}
|
||||
|
||||
if (!GetVarint64(input, &total_blob_bytes_)) {
|
||||
return Status::Corruption(class_name, "Error decoding total blob bytes");
|
||||
}
|
||||
|
||||
Slice checksum_method;
|
||||
if (!GetLengthPrefixedSlice(input, &checksum_method)) {
|
||||
return Status::Corruption(class_name, "Error decoding checksum method");
|
||||
}
|
||||
checksum_method_ = checksum_method.ToString();
|
||||
|
||||
Slice checksum_value;
|
||||
if (!GetLengthPrefixedSlice(input, &checksum_value)) {
|
||||
return Status::Corruption(class_name, "Error decoding checksum value");
|
||||
}
|
||||
checksum_value_ = checksum_value.ToString();
|
||||
|
||||
while (true) {
|
||||
uint32_t custom_field_tag = 0;
|
||||
if (!GetVarint32(input, &custom_field_tag)) {
|
||||
return Status::Corruption(class_name, "Error decoding custom field tag");
|
||||
}
|
||||
|
||||
if (custom_field_tag == kEndMarker) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (custom_field_tag & kForwardIncompatibleMask) {
|
||||
return Status::Corruption(
|
||||
class_name, "Forward incompatible custom field encountered");
|
||||
}
|
||||
|
||||
Slice custom_field_value;
|
||||
if (!GetLengthPrefixedSlice(input, &custom_field_value)) {
|
||||
return Status::Corruption(class_name,
|
||||
"Error decoding custom field value");
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string BlobFileAddition::DebugString() const {
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << *this;
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string BlobFileAddition::DebugJSON() const {
|
||||
JSONWriter jw;
|
||||
|
||||
jw << *this;
|
||||
|
||||
jw.EndObject();
|
||||
|
||||
return jw.Get();
|
||||
}
|
||||
|
||||
bool operator==(const BlobFileAddition& lhs, const BlobFileAddition& rhs) {
|
||||
return lhs.GetBlobFileNumber() == rhs.GetBlobFileNumber() &&
|
||||
lhs.GetTotalBlobCount() == rhs.GetTotalBlobCount() &&
|
||||
lhs.GetTotalBlobBytes() == rhs.GetTotalBlobBytes() &&
|
||||
lhs.GetChecksumMethod() == rhs.GetChecksumMethod() &&
|
||||
lhs.GetChecksumValue() == rhs.GetChecksumValue();
|
||||
}
|
||||
|
||||
bool operator!=(const BlobFileAddition& lhs, const BlobFileAddition& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const BlobFileAddition& blob_file_addition) {
|
||||
os << "blob_file_number: " << blob_file_addition.GetBlobFileNumber()
|
||||
<< " total_blob_count: " << blob_file_addition.GetTotalBlobCount()
|
||||
<< " total_blob_bytes: " << blob_file_addition.GetTotalBlobBytes()
|
||||
<< " checksum_method: " << blob_file_addition.GetChecksumMethod()
|
||||
<< " checksum_value: " << blob_file_addition.GetChecksumValue();
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
JSONWriter& operator<<(JSONWriter& jw,
|
||||
const BlobFileAddition& blob_file_addition) {
|
||||
jw << "BlobFileNumber" << blob_file_addition.GetBlobFileNumber()
|
||||
<< "TotalBlobCount" << blob_file_addition.GetTotalBlobCount()
|
||||
<< "TotalBlobBytes" << blob_file_addition.GetTotalBlobBytes()
|
||||
<< "ChecksumMethod" << blob_file_addition.GetChecksumMethod()
|
||||
<< "ChecksumValue" << blob_file_addition.GetChecksumValue();
|
||||
|
||||
return jw;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_constants.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class JSONWriter;
|
||||
class Slice;
|
||||
class Status;
|
||||
|
||||
class BlobFileAddition {
|
||||
public:
|
||||
BlobFileAddition() = default;
|
||||
|
||||
BlobFileAddition(uint64_t blob_file_number, uint64_t total_blob_count,
|
||||
uint64_t total_blob_bytes, std::string checksum_method,
|
||||
std::string checksum_value)
|
||||
: blob_file_number_(blob_file_number),
|
||||
total_blob_count_(total_blob_count),
|
||||
total_blob_bytes_(total_blob_bytes),
|
||||
checksum_method_(std::move(checksum_method)),
|
||||
checksum_value_(std::move(checksum_value)) {
|
||||
assert(checksum_method_.empty() == checksum_value_.empty());
|
||||
}
|
||||
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
uint64_t GetTotalBlobCount() const { return total_blob_count_; }
|
||||
uint64_t GetTotalBlobBytes() const { return total_blob_bytes_; }
|
||||
const std::string& GetChecksumMethod() const { return checksum_method_; }
|
||||
const std::string& GetChecksumValue() const { return checksum_value_; }
|
||||
|
||||
void EncodeTo(std::string* output) const;
|
||||
Status DecodeFrom(Slice* input);
|
||||
|
||||
std::string DebugString() const;
|
||||
std::string DebugJSON() const;
|
||||
|
||||
private:
|
||||
enum CustomFieldTags : uint32_t;
|
||||
|
||||
uint64_t blob_file_number_ = kInvalidBlobFileNumber;
|
||||
uint64_t total_blob_count_ = 0;
|
||||
uint64_t total_blob_bytes_ = 0;
|
||||
std::string checksum_method_;
|
||||
std::string checksum_value_;
|
||||
};
|
||||
|
||||
bool operator==(const BlobFileAddition& lhs, const BlobFileAddition& rhs);
|
||||
bool operator!=(const BlobFileAddition& lhs, const BlobFileAddition& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const BlobFileAddition& blob_file_addition);
|
||||
JSONWriter& operator<<(JSONWriter& jw,
|
||||
const BlobFileAddition& blob_file_addition);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class BlobFileAdditionTest : public testing::Test {
|
||||
public:
|
||||
static void TestEncodeDecode(const BlobFileAddition& blob_file_addition) {
|
||||
std::string encoded;
|
||||
blob_file_addition.EncodeTo(&encoded);
|
||||
|
||||
BlobFileAddition decoded;
|
||||
Slice input(encoded);
|
||||
ASSERT_OK(decoded.DecodeFrom(&input));
|
||||
|
||||
ASSERT_EQ(blob_file_addition, decoded);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BlobFileAdditionTest, Empty) {
|
||||
BlobFileAddition blob_file_addition;
|
||||
|
||||
ASSERT_EQ(blob_file_addition.GetBlobFileNumber(), kInvalidBlobFileNumber);
|
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), 0);
|
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobBytes(), 0);
|
||||
ASSERT_TRUE(blob_file_addition.GetChecksumMethod().empty());
|
||||
ASSERT_TRUE(blob_file_addition.GetChecksumValue().empty());
|
||||
|
||||
TestEncodeDecode(blob_file_addition);
|
||||
}
|
||||
|
||||
TEST_F(BlobFileAdditionTest, NonEmpty) {
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
constexpr uint64_t total_blob_count = 2;
|
||||
constexpr uint64_t total_blob_bytes = 123456;
|
||||
const std::string checksum_method("SHA1");
|
||||
const std::string checksum_value("bdb7f34a59dfa1592ce7f52e99f98c570c525cbd");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
checksum_value);
|
||||
|
||||
ASSERT_EQ(blob_file_addition.GetBlobFileNumber(), blob_file_number);
|
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), total_blob_count);
|
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(blob_file_addition.GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(blob_file_addition.GetChecksumValue(), checksum_value);
|
||||
|
||||
TestEncodeDecode(blob_file_addition);
|
||||
}
|
||||
|
||||
TEST_F(BlobFileAdditionTest, DecodeErrors) {
|
||||
std::string str;
|
||||
Slice slice(str);
|
||||
|
||||
BlobFileAddition blob_file_addition;
|
||||
|
||||
{
|
||||
const Status s = blob_file_addition.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "blob file number"));
|
||||
}
|
||||
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
PutVarint64(&str, blob_file_number);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_addition.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "total blob count"));
|
||||
}
|
||||
|
||||
constexpr uint64_t total_blob_count = 4567;
|
||||
PutVarint64(&str, total_blob_count);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_addition.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "total blob bytes"));
|
||||
}
|
||||
|
||||
constexpr uint64_t total_blob_bytes = 12345678;
|
||||
PutVarint64(&str, total_blob_bytes);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_addition.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "checksum method"));
|
||||
}
|
||||
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
PutLengthPrefixedSlice(&str, checksum_method);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_addition.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "checksum value"));
|
||||
}
|
||||
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
PutLengthPrefixedSlice(&str, checksum_value);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_addition.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "custom field tag"));
|
||||
}
|
||||
|
||||
constexpr uint32_t custom_tag = 2;
|
||||
PutVarint32(&str, custom_tag);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_addition.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "custom field value"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BlobFileAdditionTest, ForwardCompatibleCustomField) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileAddition::EncodeTo::CustomFields", [&](void* arg) {
|
||||
std::string* output = static_cast<std::string*>(arg);
|
||||
|
||||
constexpr uint32_t forward_compatible_tag = 2;
|
||||
PutVarint32(output, forward_compatible_tag);
|
||||
|
||||
PutLengthPrefixedSlice(output, "deadbeef");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr uint64_t blob_file_number = 678;
|
||||
constexpr uint64_t total_blob_count = 9999;
|
||||
constexpr uint64_t total_blob_bytes = 100000000;
|
||||
const std::string checksum_method("CRC32");
|
||||
const std::string checksum_value("3d87ff57");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
checksum_value);
|
||||
|
||||
TestEncodeDecode(blob_file_addition);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(BlobFileAdditionTest, ForwardIncompatibleCustomField) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileAddition::EncodeTo::CustomFields", [&](void* arg) {
|
||||
std::string* output = static_cast<std::string*>(arg);
|
||||
|
||||
constexpr uint32_t forward_incompatible_tag = (1 << 6) + 1;
|
||||
PutVarint32(output, forward_incompatible_tag);
|
||||
|
||||
PutLengthPrefixedSlice(output, "foobar");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr uint64_t blob_file_number = 456;
|
||||
constexpr uint64_t total_blob_count = 100;
|
||||
constexpr uint64_t total_blob_bytes = 2000000;
|
||||
const std::string checksum_method("CRC32B");
|
||||
const std::string checksum_value("6dbdf23a");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
checksum_value);
|
||||
|
||||
std::string encoded;
|
||||
blob_file_addition.EncodeTo(&encoded);
|
||||
|
||||
BlobFileAddition decoded_blob_file_addition;
|
||||
Slice input(encoded);
|
||||
const Status s = decoded_blob_file_addition.DecodeFrom(&input);
|
||||
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "Forward incompatible"));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_garbage.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "logging/event_logger.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Tags for custom fields. Note that these get persisted in the manifest,
|
||||
// so existing tags should not be modified.
|
||||
enum BlobFileGarbage::CustomFieldTags : uint32_t {
|
||||
kEndMarker,
|
||||
|
||||
// Add forward compatible fields here
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
kForwardIncompatibleMask = 1 << 6,
|
||||
|
||||
// Add forward incompatible fields here
|
||||
};
|
||||
|
||||
void BlobFileGarbage::EncodeTo(std::string* output) const {
|
||||
PutVarint64(output, blob_file_number_);
|
||||
PutVarint64(output, garbage_blob_count_);
|
||||
PutVarint64(output, garbage_blob_bytes_);
|
||||
|
||||
// Encode any custom fields here. The format to use is a Varint32 tag (see
|
||||
// CustomFieldTags above) followed by a length prefixed slice. Unknown custom
|
||||
// fields will be ignored during decoding unless they're in the forward
|
||||
// incompatible range.
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("BlobFileGarbage::EncodeTo::CustomFields", output);
|
||||
|
||||
PutVarint32(output, kEndMarker);
|
||||
}
|
||||
|
||||
Status BlobFileGarbage::DecodeFrom(Slice* input) {
|
||||
constexpr char class_name[] = "BlobFileGarbage";
|
||||
|
||||
if (!GetVarint64(input, &blob_file_number_)) {
|
||||
return Status::Corruption(class_name, "Error decoding blob file number");
|
||||
}
|
||||
|
||||
if (!GetVarint64(input, &garbage_blob_count_)) {
|
||||
return Status::Corruption(class_name, "Error decoding garbage blob count");
|
||||
}
|
||||
|
||||
if (!GetVarint64(input, &garbage_blob_bytes_)) {
|
||||
return Status::Corruption(class_name, "Error decoding garbage blob bytes");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
uint32_t custom_field_tag = 0;
|
||||
if (!GetVarint32(input, &custom_field_tag)) {
|
||||
return Status::Corruption(class_name, "Error decoding custom field tag");
|
||||
}
|
||||
|
||||
if (custom_field_tag == kEndMarker) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (custom_field_tag & kForwardIncompatibleMask) {
|
||||
return Status::Corruption(
|
||||
class_name, "Forward incompatible custom field encountered");
|
||||
}
|
||||
|
||||
Slice custom_field_value;
|
||||
if (!GetLengthPrefixedSlice(input, &custom_field_value)) {
|
||||
return Status::Corruption(class_name,
|
||||
"Error decoding custom field value");
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::string BlobFileGarbage::DebugString() const {
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << *this;
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::string BlobFileGarbage::DebugJSON() const {
|
||||
JSONWriter jw;
|
||||
|
||||
jw << *this;
|
||||
|
||||
jw.EndObject();
|
||||
|
||||
return jw.Get();
|
||||
}
|
||||
|
||||
bool operator==(const BlobFileGarbage& lhs, const BlobFileGarbage& rhs) {
|
||||
return lhs.GetBlobFileNumber() == rhs.GetBlobFileNumber() &&
|
||||
lhs.GetGarbageBlobCount() == rhs.GetGarbageBlobCount() &&
|
||||
lhs.GetGarbageBlobBytes() == rhs.GetGarbageBlobBytes();
|
||||
}
|
||||
|
||||
bool operator!=(const BlobFileGarbage& lhs, const BlobFileGarbage& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const BlobFileGarbage& blob_file_garbage) {
|
||||
os << "blob_file_number: " << blob_file_garbage.GetBlobFileNumber()
|
||||
<< " garbage_blob_count: " << blob_file_garbage.GetGarbageBlobCount()
|
||||
<< " garbage_blob_bytes: " << blob_file_garbage.GetGarbageBlobBytes();
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
JSONWriter& operator<<(JSONWriter& jw,
|
||||
const BlobFileGarbage& blob_file_garbage) {
|
||||
jw << "BlobFileNumber" << blob_file_garbage.GetBlobFileNumber()
|
||||
<< "GarbageBlobCount" << blob_file_garbage.GetGarbageBlobCount()
|
||||
<< "GarbageBlobBytes" << blob_file_garbage.GetGarbageBlobBytes();
|
||||
|
||||
return jw;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_constants.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class JSONWriter;
|
||||
class Slice;
|
||||
class Status;
|
||||
|
||||
class BlobFileGarbage {
|
||||
public:
|
||||
BlobFileGarbage() = default;
|
||||
|
||||
BlobFileGarbage(uint64_t blob_file_number, uint64_t garbage_blob_count,
|
||||
uint64_t garbage_blob_bytes)
|
||||
: blob_file_number_(blob_file_number),
|
||||
garbage_blob_count_(garbage_blob_count),
|
||||
garbage_blob_bytes_(garbage_blob_bytes) {}
|
||||
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
uint64_t GetGarbageBlobCount() const { return garbage_blob_count_; }
|
||||
uint64_t GetGarbageBlobBytes() const { return garbage_blob_bytes_; }
|
||||
|
||||
void EncodeTo(std::string* output) const;
|
||||
Status DecodeFrom(Slice* input);
|
||||
|
||||
std::string DebugString() const;
|
||||
std::string DebugJSON() const;
|
||||
|
||||
private:
|
||||
enum CustomFieldTags : uint32_t;
|
||||
|
||||
uint64_t blob_file_number_ = kInvalidBlobFileNumber;
|
||||
uint64_t garbage_blob_count_ = 0;
|
||||
uint64_t garbage_blob_bytes_ = 0;
|
||||
};
|
||||
|
||||
bool operator==(const BlobFileGarbage& lhs, const BlobFileGarbage& rhs);
|
||||
bool operator!=(const BlobFileGarbage& lhs, const BlobFileGarbage& rhs);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const BlobFileGarbage& blob_file_garbage);
|
||||
JSONWriter& operator<<(JSONWriter& jw,
|
||||
const BlobFileGarbage& blob_file_garbage);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_garbage.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class BlobFileGarbageTest : public testing::Test {
|
||||
public:
|
||||
static void TestEncodeDecode(const BlobFileGarbage& blob_file_garbage) {
|
||||
std::string encoded;
|
||||
blob_file_garbage.EncodeTo(&encoded);
|
||||
|
||||
BlobFileGarbage decoded;
|
||||
Slice input(encoded);
|
||||
ASSERT_OK(decoded.DecodeFrom(&input));
|
||||
|
||||
ASSERT_EQ(blob_file_garbage, decoded);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(BlobFileGarbageTest, Empty) {
|
||||
BlobFileGarbage blob_file_garbage;
|
||||
|
||||
ASSERT_EQ(blob_file_garbage.GetBlobFileNumber(), kInvalidBlobFileNumber);
|
||||
ASSERT_EQ(blob_file_garbage.GetGarbageBlobCount(), 0);
|
||||
ASSERT_EQ(blob_file_garbage.GetGarbageBlobBytes(), 0);
|
||||
|
||||
TestEncodeDecode(blob_file_garbage);
|
||||
}
|
||||
|
||||
TEST_F(BlobFileGarbageTest, NonEmpty) {
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
constexpr uint64_t garbage_blob_count = 1;
|
||||
constexpr uint64_t garbage_blob_bytes = 9876;
|
||||
|
||||
BlobFileGarbage blob_file_garbage(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
ASSERT_EQ(blob_file_garbage.GetBlobFileNumber(), blob_file_number);
|
||||
ASSERT_EQ(blob_file_garbage.GetGarbageBlobCount(), garbage_blob_count);
|
||||
ASSERT_EQ(blob_file_garbage.GetGarbageBlobBytes(), garbage_blob_bytes);
|
||||
|
||||
TestEncodeDecode(blob_file_garbage);
|
||||
}
|
||||
|
||||
TEST_F(BlobFileGarbageTest, DecodeErrors) {
|
||||
std::string str;
|
||||
Slice slice(str);
|
||||
|
||||
BlobFileGarbage blob_file_garbage;
|
||||
|
||||
{
|
||||
const Status s = blob_file_garbage.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "blob file number"));
|
||||
}
|
||||
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
PutVarint64(&str, blob_file_number);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_garbage.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "garbage blob count"));
|
||||
}
|
||||
|
||||
constexpr uint64_t garbage_blob_count = 4567;
|
||||
PutVarint64(&str, garbage_blob_count);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_garbage.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "garbage blob bytes"));
|
||||
}
|
||||
|
||||
constexpr uint64_t garbage_blob_bytes = 12345678;
|
||||
PutVarint64(&str, garbage_blob_bytes);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_garbage.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "custom field tag"));
|
||||
}
|
||||
|
||||
constexpr uint32_t custom_tag = 2;
|
||||
PutVarint32(&str, custom_tag);
|
||||
slice = str;
|
||||
|
||||
{
|
||||
const Status s = blob_file_garbage.DecodeFrom(&slice);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "custom field value"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BlobFileGarbageTest, ForwardCompatibleCustomField) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileGarbage::EncodeTo::CustomFields", [&](void* arg) {
|
||||
std::string* output = static_cast<std::string*>(arg);
|
||||
|
||||
constexpr uint32_t forward_compatible_tag = 2;
|
||||
PutVarint32(output, forward_compatible_tag);
|
||||
|
||||
PutLengthPrefixedSlice(output, "deadbeef");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr uint64_t blob_file_number = 678;
|
||||
constexpr uint64_t garbage_blob_count = 9999;
|
||||
constexpr uint64_t garbage_blob_bytes = 100000000;
|
||||
|
||||
BlobFileGarbage blob_file_garbage(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
TestEncodeDecode(blob_file_garbage);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(BlobFileGarbageTest, ForwardIncompatibleCustomField) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileGarbage::EncodeTo::CustomFields", [&](void* arg) {
|
||||
std::string* output = static_cast<std::string*>(arg);
|
||||
|
||||
constexpr uint32_t forward_incompatible_tag = (1 << 6) + 1;
|
||||
PutVarint32(output, forward_incompatible_tag);
|
||||
|
||||
PutLengthPrefixedSlice(output, "foobar");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr uint64_t blob_file_number = 456;
|
||||
constexpr uint64_t garbage_blob_count = 100;
|
||||
constexpr uint64_t garbage_blob_bytes = 2000000;
|
||||
|
||||
BlobFileGarbage blob_file_garbage(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
std::string encoded;
|
||||
blob_file_garbage.EncodeTo(&encoded);
|
||||
|
||||
BlobFileGarbage decoded_blob_file_addition;
|
||||
Slice input(encoded);
|
||||
const Status s = decoded_blob_file_addition.DecodeFrom(&input);
|
||||
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "Forward incompatible"));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
SharedBlobFileMetaData::~SharedBlobFileMetaData() {
|
||||
// TODO: add the blob file to the list of obsolete files here
|
||||
}
|
||||
|
||||
std::string SharedBlobFileMetaData::DebugString() const {
|
||||
std::ostringstream oss;
|
||||
oss << (*this);
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const SharedBlobFileMetaData& shared_meta) {
|
||||
os << "blob_file_number: " << shared_meta.GetBlobFileNumber()
|
||||
<< " total_blob_count: " << shared_meta.GetTotalBlobCount()
|
||||
<< " total_blob_bytes: " << shared_meta.GetTotalBlobBytes()
|
||||
<< " checksum_method: " << shared_meta.GetChecksumMethod()
|
||||
<< " checksum_value: " << shared_meta.GetChecksumValue();
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
std::string BlobFileMetaData::DebugString() const {
|
||||
std::ostringstream oss;
|
||||
oss << (*this);
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const BlobFileMetaData& meta) {
|
||||
const auto& shared_meta = meta.GetSharedMeta();
|
||||
assert(shared_meta);
|
||||
|
||||
os << (*shared_meta) << " garbage_blob_count: " << meta.GetGarbageBlobCount()
|
||||
<< " garbage_blob_bytes: " << meta.GetGarbageBlobBytes();
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// SharedBlobFileMetaData represents the immutable part of blob files' metadata,
|
||||
// like the blob file number, total number and size of blobs, or checksum
|
||||
// method and value. There is supposed to be one object of this class per blob
|
||||
// file (shared across all versions that include the blob file in question);
|
||||
// hence, the type is neither copyable nor movable. A blob file can be marked
|
||||
// obsolete when the corresponding SharedBlobFileMetaData object is destroyed.
|
||||
|
||||
class SharedBlobFileMetaData {
|
||||
public:
|
||||
SharedBlobFileMetaData(uint64_t blob_file_number, uint64_t total_blob_count,
|
||||
uint64_t total_blob_bytes, std::string checksum_method,
|
||||
std::string checksum_value)
|
||||
: blob_file_number_(blob_file_number),
|
||||
total_blob_count_(total_blob_count),
|
||||
total_blob_bytes_(total_blob_bytes),
|
||||
checksum_method_(std::move(checksum_method)),
|
||||
checksum_value_(std::move(checksum_value)) {
|
||||
assert(checksum_method_.empty() == checksum_value_.empty());
|
||||
}
|
||||
|
||||
~SharedBlobFileMetaData();
|
||||
|
||||
SharedBlobFileMetaData(const SharedBlobFileMetaData&) = delete;
|
||||
SharedBlobFileMetaData& operator=(const SharedBlobFileMetaData&) = delete;
|
||||
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
uint64_t GetTotalBlobCount() const { return total_blob_count_; }
|
||||
uint64_t GetTotalBlobBytes() const { return total_blob_bytes_; }
|
||||
const std::string& GetChecksumMethod() const { return checksum_method_; }
|
||||
const std::string& GetChecksumValue() const { return checksum_value_; }
|
||||
|
||||
std::string DebugString() const;
|
||||
|
||||
private:
|
||||
uint64_t blob_file_number_;
|
||||
uint64_t total_blob_count_;
|
||||
uint64_t total_blob_bytes_;
|
||||
std::string checksum_method_;
|
||||
std::string checksum_value_;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
const SharedBlobFileMetaData& shared_meta);
|
||||
|
||||
// BlobFileMetaData contains the part of the metadata for blob files that can
|
||||
// vary across versions, like the amount of garbage in the blob file. In
|
||||
// addition, BlobFileMetaData objects point to and share the ownership of the
|
||||
// SharedBlobFileMetaData object for the corresponding blob file. Similarly to
|
||||
// SharedBlobFileMetaData, BlobFileMetaData are not copyable or movable. They
|
||||
// are meant to be jointly owned by the versions in which the blob file has the
|
||||
// same (immutable *and* mutable) state.
|
||||
|
||||
class BlobFileMetaData {
|
||||
public:
|
||||
BlobFileMetaData(std::shared_ptr<SharedBlobFileMetaData> shared_meta,
|
||||
uint64_t garbage_blob_count, uint64_t garbage_blob_bytes)
|
||||
: shared_meta_(std::move(shared_meta)),
|
||||
garbage_blob_count_(garbage_blob_count),
|
||||
garbage_blob_bytes_(garbage_blob_bytes) {
|
||||
assert(shared_meta_);
|
||||
assert(garbage_blob_count_ <= shared_meta_->GetTotalBlobCount());
|
||||
assert(garbage_blob_bytes_ <= shared_meta_->GetTotalBlobBytes());
|
||||
}
|
||||
|
||||
~BlobFileMetaData() = default;
|
||||
|
||||
BlobFileMetaData(const BlobFileMetaData&) = delete;
|
||||
BlobFileMetaData& operator=(const BlobFileMetaData&) = delete;
|
||||
|
||||
const std::shared_ptr<SharedBlobFileMetaData>& GetSharedMeta() const {
|
||||
return shared_meta_;
|
||||
}
|
||||
|
||||
uint64_t GetBlobFileNumber() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetBlobFileNumber();
|
||||
}
|
||||
uint64_t GetTotalBlobCount() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetTotalBlobCount();
|
||||
}
|
||||
uint64_t GetTotalBlobBytes() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetTotalBlobBytes();
|
||||
}
|
||||
const std::string& GetChecksumMethod() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetChecksumMethod();
|
||||
}
|
||||
const std::string& GetChecksumValue() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetChecksumValue();
|
||||
}
|
||||
|
||||
uint64_t GetGarbageBlobCount() const { return garbage_blob_count_; }
|
||||
uint64_t GetGarbageBlobBytes() const { return garbage_blob_bytes_; }
|
||||
|
||||
std::string DebugString() const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<SharedBlobFileMetaData> shared_meta_;
|
||||
uint64_t garbage_blob_count_;
|
||||
uint64_t garbage_blob_bytes_;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const BlobFileMetaData& meta);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
File renamed without changes.
File renamed without changes.
+22
-11
@@ -81,10 +81,11 @@ Status BuildTable(
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
bool paranoid_file_checks, InternalStats* internal_stats,
|
||||
TableFileCreationReason reason, EventLogger* event_logger, int job_id,
|
||||
const Env::IOPriority io_priority, TableProperties* table_properties,
|
||||
int level, const uint64_t creation_time, const uint64_t oldest_key_time,
|
||||
Env::WriteLifeTimeHint write_hint, const uint64_t file_creation_time) {
|
||||
TableFileCreationReason reason, IOStatus* io_status,
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint,
|
||||
const uint64_t file_creation_time) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -133,7 +134,7 @@ Status BuildTable(
|
||||
|
||||
file_writer.reset(new WritableFileWriter(
|
||||
std::move(file), fname, file_options, env, ioptions.statistics,
|
||||
ioptions.listeners, ioptions.sst_file_checksum_func));
|
||||
ioptions.listeners, ioptions.file_checksum_gen_factory));
|
||||
|
||||
builder = NewTableBuilder(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
@@ -185,11 +186,13 @@ Status BuildTable(
|
||||
tp = builder->GetTableProperties();
|
||||
bool empty = builder->NumEntries() == 0 && tp.num_range_deletions == 0;
|
||||
s = c_iter.status();
|
||||
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
|
||||
if (!s.ok() || empty) {
|
||||
builder->Abandon();
|
||||
} else {
|
||||
s = builder->Finish();
|
||||
}
|
||||
*io_status = builder->io_status();
|
||||
|
||||
if (s.ok() && !empty) {
|
||||
uint64_t file_size = builder->FileSize();
|
||||
@@ -200,20 +203,28 @@ Status BuildTable(
|
||||
if (table_properties) {
|
||||
*table_properties = tp;
|
||||
}
|
||||
// Add the checksum information to file metadata.
|
||||
meta->file_checksum = builder->GetFileChecksum();
|
||||
meta->file_checksum_func_name = builder->GetFileChecksumFuncName();
|
||||
}
|
||||
delete builder;
|
||||
|
||||
// Finish and check for file errors
|
||||
if (s.ok() && !empty) {
|
||||
StopWatch sw(env, ioptions.statistics, TABLE_SYNC_MICROS);
|
||||
s = file_writer->Sync(ioptions.use_fsync);
|
||||
*io_status = file_writer->Sync(ioptions.use_fsync);
|
||||
}
|
||||
if (s.ok() && !empty) {
|
||||
s = file_writer->Close();
|
||||
if (io_status->ok() && !empty) {
|
||||
*io_status = file_writer->Close();
|
||||
}
|
||||
if (io_status->ok() && !empty) {
|
||||
// Add the checksum information to file metadata.
|
||||
meta->file_checksum = file_writer->GetFileChecksum();
|
||||
meta->file_checksum_func_name = file_writer->GetFileChecksumFuncName();
|
||||
}
|
||||
|
||||
if (!io_status->ok()) {
|
||||
s = *io_status;
|
||||
}
|
||||
|
||||
// TODO Also check the IO status when create the Iterator.
|
||||
|
||||
if (s.ok() && !empty) {
|
||||
// Verify that the table is usable
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ extern Status BuildTable(
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
IOStatus* io_status, EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr, int level = -1,
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
|
||||
+51
-11
@@ -471,6 +471,17 @@ void SuperVersionUnrefHandle(void* ptr) {
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
std::vector<std::string> ColumnFamilyData::GetDbPaths() const {
|
||||
std::vector<std::string> paths;
|
||||
paths.reserve(ioptions_.cf_paths.size());
|
||||
for (const DbPath& db_path : ioptions_.cf_paths) {
|
||||
paths.emplace_back(db_path.path);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
const uint32_t ColumnFamilyData::kDummyColumnFamilyDataId = port::kMaxUint32;
|
||||
|
||||
ColumnFamilyData::ColumnFamilyData(
|
||||
uint32_t id, const std::string& name, Version* _dummy_versions,
|
||||
Cache* _table_cache, WriteBufferManager* write_buffer_manager,
|
||||
@@ -507,7 +518,23 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
queued_for_compaction_(false),
|
||||
prev_compaction_needed_bytes_(0),
|
||||
allow_2pc_(db_options.allow_2pc),
|
||||
last_memtable_id_(0) {
|
||||
last_memtable_id_(0),
|
||||
db_paths_registered_(false) {
|
||||
if (id_ != kDummyColumnFamilyDataId) {
|
||||
// TODO(cc): RegisterDbPaths can be expensive, considering moving it
|
||||
// outside of this constructor which might be called with db mutex held.
|
||||
// TODO(cc): considering using ioptions_.fs, currently some tests rely on
|
||||
// EnvWrapper, that's the main reason why we use env here.
|
||||
Status s = ioptions_.env->RegisterDbPaths(GetDbPaths());
|
||||
if (s.ok()) {
|
||||
db_paths_registered_ = true;
|
||||
} else {
|
||||
ROCKS_LOG_ERROR(
|
||||
ioptions_.info_log,
|
||||
"Failed to register data paths of column family (id: %d, name: %s)",
|
||||
id_, name_.c_str());
|
||||
}
|
||||
}
|
||||
Ref();
|
||||
|
||||
// Convert user defined table properties collector factories to internal ones.
|
||||
@@ -601,6 +628,18 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
for (MemTable* m : to_delete) {
|
||||
delete m;
|
||||
}
|
||||
|
||||
if (db_paths_registered_) {
|
||||
// TODO(cc): considering using ioptions_.fs, currently some tests rely on
|
||||
// EnvWrapper, that's the main reason why we use env here.
|
||||
Status s = ioptions_.env->UnregisterDbPaths(GetDbPaths());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(
|
||||
ioptions_.info_log,
|
||||
"Failed to unregister data paths of column family (id: %d, name: %s)",
|
||||
id_, name_.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ColumnFamilyData::UnrefAndTryDelete() {
|
||||
@@ -1321,7 +1360,7 @@ Env::WriteLifeTimeHint ColumnFamilyData::CalculateSSTWriteHint(int level) {
|
||||
}
|
||||
|
||||
Status ColumnFamilyData::AddDirectories(
|
||||
std::map<std::string, std::shared_ptr<Directory>>* created_dirs) {
|
||||
std::map<std::string, std::shared_ptr<FSDirectory>>* created_dirs) {
|
||||
Status s;
|
||||
assert(created_dirs != nullptr);
|
||||
assert(data_dirs_.empty());
|
||||
@@ -1329,8 +1368,8 @@ Status ColumnFamilyData::AddDirectories(
|
||||
auto existing_dir = created_dirs->find(p.path);
|
||||
|
||||
if (existing_dir == created_dirs->end()) {
|
||||
std::unique_ptr<Directory> path_directory;
|
||||
s = DBImpl::CreateAndNewDirectory(ioptions_.env, p.path, &path_directory);
|
||||
std::unique_ptr<FSDirectory> path_directory;
|
||||
s = DBImpl::CreateAndNewDirectory(ioptions_.fs, p.path, &path_directory);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1345,7 +1384,7 @@ Status ColumnFamilyData::AddDirectories(
|
||||
return s;
|
||||
}
|
||||
|
||||
Directory* ColumnFamilyData::GetDataDir(size_t path_id) const {
|
||||
FSDirectory* ColumnFamilyData::GetDataDir(size_t path_id) const {
|
||||
if (data_dirs_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1358,20 +1397,21 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
const ImmutableDBOptions* db_options,
|
||||
const FileOptions& file_options,
|
||||
Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
WriteBufferManager* _write_buffer_manager,
|
||||
WriteController* _write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer)
|
||||
: max_column_family_(0),
|
||||
dummy_cfd_(new ColumnFamilyData(
|
||||
0, "", nullptr, nullptr, nullptr, ColumnFamilyOptions(), *db_options,
|
||||
file_options, nullptr, block_cache_tracer)),
|
||||
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
|
||||
nullptr, ColumnFamilyOptions(), *db_options, file_options, nullptr,
|
||||
block_cache_tracer)),
|
||||
default_cfd_cache_(nullptr),
|
||||
db_name_(dbname),
|
||||
db_options_(db_options),
|
||||
file_options_(file_options),
|
||||
table_cache_(table_cache),
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
write_controller_(write_controller),
|
||||
write_buffer_manager_(_write_buffer_manager),
|
||||
write_controller_(_write_controller),
|
||||
block_cache_tracer_(block_cache_tracer) {
|
||||
// initialize linked list
|
||||
dummy_cfd_->prev_ = dummy_cfd_;
|
||||
|
||||
+14
-5
@@ -500,14 +500,15 @@ class ColumnFamilyData {
|
||||
// created_dirs remembers directory created, so that we don't need to call
|
||||
// the same data creation operation again.
|
||||
Status AddDirectories(
|
||||
std::map<std::string, std::shared_ptr<Directory>>* created_dirs);
|
||||
std::map<std::string, std::shared_ptr<FSDirectory>>* created_dirs);
|
||||
|
||||
Directory* GetDataDir(size_t path_id) const;
|
||||
FSDirectory* GetDataDir(size_t path_id) const;
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
static const uint32_t kDummyColumnFamilyDataId;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
Version* dummy_versions, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
@@ -517,6 +518,8 @@ class ColumnFamilyData {
|
||||
ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer);
|
||||
|
||||
std::vector<std::string> GetDbPaths() const;
|
||||
|
||||
uint32_t id_;
|
||||
const std::string name_;
|
||||
Version* dummy_versions_; // Head of circular doubly-linked list of versions.
|
||||
@@ -592,7 +595,9 @@ class ColumnFamilyData {
|
||||
std::atomic<uint64_t> last_memtable_id_;
|
||||
|
||||
// Directories corresponding to cf_paths.
|
||||
std::vector<std::shared_ptr<Directory>> data_dirs_;
|
||||
std::vector<std::shared_ptr<FSDirectory>> data_dirs_;
|
||||
|
||||
bool db_paths_registered_;
|
||||
};
|
||||
|
||||
// ColumnFamilySet has interesting thread-safety requirements
|
||||
@@ -642,8 +647,8 @@ class ColumnFamilySet {
|
||||
ColumnFamilySet(const std::string& dbname,
|
||||
const ImmutableDBOptions* db_options,
|
||||
const FileOptions& file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
WriteBufferManager* _write_buffer_manager,
|
||||
WriteController* _write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer);
|
||||
~ColumnFamilySet();
|
||||
|
||||
@@ -673,6 +678,10 @@ class ColumnFamilySet {
|
||||
|
||||
Cache* get_table_cache() { return table_cache_; }
|
||||
|
||||
WriteBufferManager* write_buffer_manager() { return write_buffer_manager_; }
|
||||
|
||||
WriteController* write_controller() { return write_controller_; }
|
||||
|
||||
private:
|
||||
friend class ColumnFamilyData;
|
||||
// helper function that gets called from cfd destructor
|
||||
|
||||
@@ -821,7 +821,7 @@ TEST_P(ColumnFamilyTest, BulkAddDrop) {
|
||||
}
|
||||
|
||||
TEST_P(ColumnFamilyTest, DropTest) {
|
||||
// first iteration - dont reopen DB before dropping
|
||||
// first iteration - don't reopen DB before dropping
|
||||
// second iteration - reopen DB before dropping
|
||||
for (int iter = 0; iter < 2; ++iter) {
|
||||
Open({"default"});
|
||||
|
||||
@@ -37,7 +37,7 @@ Status CompactedDBImpl::Get(const ReadOptions& options, ColumnFamilyHandle*,
|
||||
const Slice& key, PinnableSlice* value) {
|
||||
GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
|
||||
GetContext::kNotFound, key, value, nullptr, nullptr,
|
||||
true, nullptr, nullptr);
|
||||
nullptr, true, nullptr, nullptr);
|
||||
LookupKey lkey(key, kMaxSequenceNumber);
|
||||
files_.files[FindFile(key)].fd.table_reader->Get(options, lkey.internal_key(),
|
||||
&get_context, nullptr);
|
||||
@@ -70,7 +70,7 @@ std::vector<Status> CompactedDBImpl::MultiGet(const ReadOptions& options,
|
||||
std::string& value = (*values)[idx];
|
||||
GetContext get_context(user_comparator_, nullptr, nullptr, nullptr,
|
||||
GetContext::kNotFound, keys[idx], &pinnable_val,
|
||||
nullptr, nullptr, true, nullptr, nullptr);
|
||||
nullptr, nullptr, nullptr, true, nullptr, nullptr);
|
||||
LookupKey lkey(keys[idx], kMaxSequenceNumber);
|
||||
r->Get(options, lkey.internal_key(), &get_context, nullptr);
|
||||
value.assign(pinnable_val.data(), pinnable_val.size());
|
||||
|
||||
@@ -298,7 +298,7 @@ CompactionJob::CompactionJob(
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory, Statistics* stats,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
@@ -613,8 +613,13 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
|
||||
IOStatus io_s;
|
||||
if (status.ok() && output_directory_) {
|
||||
status = output_directory_->Fsync();
|
||||
io_s = output_directory_->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
status = io_s;
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
@@ -713,9 +718,13 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
cfd->internal_stats()->AddCompactionStats(
|
||||
compact_->compaction->output_level(), thread_pri_, compaction_stats_);
|
||||
|
||||
versions_->SetIOStatusOK();
|
||||
if (status.ok()) {
|
||||
status = InstallCompactionResults(mutable_cf_options);
|
||||
}
|
||||
if (!versions_->io_status().ok()) {
|
||||
io_status_ = versions_->io_status();
|
||||
}
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
auto vstorage = cfd->current()->storage_info();
|
||||
const auto& stats = compaction_stats_;
|
||||
@@ -1294,25 +1303,35 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
} else {
|
||||
sub_compact->builder->Abandon();
|
||||
}
|
||||
if (!sub_compact->builder->io_status().ok()) {
|
||||
io_status_ = sub_compact->builder->io_status();
|
||||
s = io_status_;
|
||||
}
|
||||
const uint64_t current_bytes = sub_compact->builder->FileSize();
|
||||
if (s.ok()) {
|
||||
// Add the checksum information to file metadata.
|
||||
meta->file_checksum = sub_compact->builder->GetFileChecksum();
|
||||
meta->file_checksum_func_name =
|
||||
sub_compact->builder->GetFileChecksumFuncName();
|
||||
|
||||
meta->fd.file_size = current_bytes;
|
||||
}
|
||||
sub_compact->current_output()->finished = true;
|
||||
sub_compact->total_bytes += current_bytes;
|
||||
|
||||
// Finish and check for file errors
|
||||
IOStatus io_s;
|
||||
if (s.ok()) {
|
||||
StopWatch sw(env_, stats_, COMPACTION_OUTFILE_SYNC_MICROS);
|
||||
s = sub_compact->outfile->Sync(db_options_.use_fsync);
|
||||
io_s = sub_compact->outfile->Sync(db_options_.use_fsync);
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = sub_compact->outfile->Close();
|
||||
if (io_s.ok()) {
|
||||
io_s = sub_compact->outfile->Close();
|
||||
}
|
||||
if (io_s.ok()) {
|
||||
// Add the checksum information to file metadata.
|
||||
meta->file_checksum = sub_compact->outfile->GetFileChecksum();
|
||||
meta->file_checksum_func_name =
|
||||
sub_compact->outfile->GetFileChecksumFuncName();
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
}
|
||||
sub_compact->outfile.reset();
|
||||
|
||||
@@ -1514,7 +1533,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->outfile.reset(
|
||||
new WritableFileWriter(std::move(writable_file), fname, file_options_,
|
||||
env_, db_options_.statistics.get(), listeners,
|
||||
db_options_.sst_file_checksum_func.get()));
|
||||
db_options_.file_checksum_gen_factory.get()));
|
||||
|
||||
// If the Column family flag is to only optimize filters for hits,
|
||||
// we can skip creating filters if this is the bottommost_level where
|
||||
|
||||
@@ -67,8 +67,8 @@ class CompactionJob {
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
LogBuffer* log_buffer, Directory* db_directory,
|
||||
Directory* output_directory, Statistics* stats,
|
||||
LogBuffer* log_buffer, FSDirectory* db_directory,
|
||||
FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
@@ -100,6 +100,9 @@ class CompactionJob {
|
||||
// Add compaction input/output to the current version
|
||||
Status Install(const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Return the IO status
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
private:
|
||||
struct SubcompactionState;
|
||||
|
||||
@@ -161,8 +164,8 @@ class CompactionJob {
|
||||
const std::atomic<bool>* manual_compaction_paused_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
LogBuffer* log_buffer_;
|
||||
Directory* db_directory_;
|
||||
Directory* output_directory_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* output_directory_;
|
||||
Statistics* stats_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
ErrorHandler* db_error_handler_;
|
||||
@@ -193,6 +196,7 @@ class CompactionJob {
|
||||
std::vector<uint64_t> sizes_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
Env::Priority thread_pri_;
|
||||
IOStatus io_status_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "db/blob_index.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_job.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
@@ -281,7 +281,7 @@ class CompactionJobTest : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
@@ -314,11 +314,11 @@ class CompactionJobTest : public testing::Test {
|
||||
num_input_files += level_files.size();
|
||||
}
|
||||
|
||||
Compaction compaction(cfd->current()->storage_info(), *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
compaction_input_files, output_level, 1024 * 1024,
|
||||
10 * 1024 * 1024, 0, kNoCompression,
|
||||
cfd->ioptions()->compression_opts, 0, {}, true);
|
||||
Compaction compaction(
|
||||
cfd->current()->storage_info(), *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(), compaction_input_files, output_level,
|
||||
1024 * 1024, 10 * 1024 * 1024, 0, kNoCompression,
|
||||
cfd->GetLatestMutableCFOptions()->compression_opts, 0, {}, true);
|
||||
compaction.SetInputVersion(cfd->current());
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
|
||||
@@ -110,9 +110,9 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
|
||||
// If bottommost_compression is set and we are compacting to the
|
||||
// bottommost level then we should use it.
|
||||
if (ioptions.bottommost_compression != kDisableCompressionOption &&
|
||||
if (mutable_cf_options.bottommost_compression != kDisableCompressionOption &&
|
||||
level >= (vstorage->num_non_empty_levels() - 1)) {
|
||||
return ioptions.bottommost_compression;
|
||||
return mutable_cf_options.bottommost_compression;
|
||||
}
|
||||
// If the user has specified a different compression level for each level,
|
||||
// then pick the compression for that level.
|
||||
@@ -132,22 +132,22 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
}
|
||||
}
|
||||
|
||||
CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions,
|
||||
CompressionOptions GetCompressionOptions(const MutableCFOptions& cf_options,
|
||||
const VersionStorageInfo* vstorage,
|
||||
int level,
|
||||
const bool enable_compression) {
|
||||
if (!enable_compression) {
|
||||
return ioptions.compression_opts;
|
||||
return cf_options.compression_opts;
|
||||
}
|
||||
// If bottommost_compression is set and we are compacting to the
|
||||
// bottommost level then we should use the specified compression options
|
||||
// for the bottmomost_compression.
|
||||
if (ioptions.bottommost_compression != kDisableCompressionOption &&
|
||||
if (cf_options.bottommost_compression != kDisableCompressionOption &&
|
||||
level >= (vstorage->num_non_empty_levels() - 1) &&
|
||||
ioptions.bottommost_compression_opts.enabled) {
|
||||
return ioptions.bottommost_compression_opts;
|
||||
cf_options.bottommost_compression_opts.enabled) {
|
||||
return cf_options.bottommost_compression_opts;
|
||||
}
|
||||
return ioptions.compression_opts;
|
||||
return cf_options.compression_opts;
|
||||
}
|
||||
|
||||
CompactionPicker::CompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
@@ -359,7 +359,7 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
vstorage, ioptions_, mutable_cf_options, input_files, output_level,
|
||||
compact_options.output_file_size_limit,
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id, compression_type,
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, true);
|
||||
RegisterCompaction(c);
|
||||
@@ -634,7 +634,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_range_options.max_subcompactions, /* grandparents */ {},
|
||||
/* is manual */ true);
|
||||
RegisterCompaction(c);
|
||||
@@ -787,7 +787,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
/* is manual compaction */ true);
|
||||
|
||||
|
||||
@@ -305,9 +305,9 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
int level, int base_level,
|
||||
const bool enable_compression = true);
|
||||
|
||||
CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions,
|
||||
const VersionStorageInfo* vstorage,
|
||||
int level,
|
||||
const bool enable_compression = true);
|
||||
CompressionOptions GetCompressionOptions(
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const VersionStorageInfo* vstorage, int level,
|
||||
const bool enable_compression = true);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -78,9 +78,9 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
creation_time >= (current_time - mutable_cf_options.ttl)) {
|
||||
break;
|
||||
}
|
||||
total_size -= f->compensated_file_size;
|
||||
inputs[0].files.push_back(f);
|
||||
}
|
||||
total_size -= f->compensated_file_size;
|
||||
inputs[0].files.push_back(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,8 +107,9 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
|
||||
kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0,
|
||||
{}, /* is manual */ false, vstorage->CompactionScore(0),
|
||||
kNoCompression, mutable_cf_options.compression_opts,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOTtl);
|
||||
return c;
|
||||
}
|
||||
@@ -148,7 +149,7 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
16 * 1024 * 1024 /* output file size limit */,
|
||||
0 /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID */, mutable_cf_options.compression,
|
||||
ioptions_.compression_opts, 0 /* max_subcompactions */, {},
|
||||
mutable_cf_options.compression_opts, 0 /* max_subcompactions */, {},
|
||||
/* is manual */ false, vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false,
|
||||
CompactionReason::kFIFOReduceNumFiles);
|
||||
@@ -196,8 +197,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
|
||||
kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0,
|
||||
{}, /* is manual */ false, vstorage->CompactionScore(0),
|
||||
kNoCompression, mutable_cf_options.compression_opts,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOMaxSize);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -250,7 +250,6 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
cf_name_, vstorage_, &start_level_, &output_level_,
|
||||
&start_level_inputs_);
|
||||
if (!start_level_inputs_.empty()) {
|
||||
is_manual_ = true;
|
||||
compaction_reason_ = CompactionReason::kFilesMarkedForCompaction;
|
||||
return;
|
||||
}
|
||||
@@ -384,7 +383,7 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
GetPathId(ioptions_, mutable_cf_options_, output_level_),
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level_, vstorage_->base_level()),
|
||||
GetCompressionOptions(ioptions_, vstorage_, output_level_),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level_),
|
||||
/* max_subcompactions */ 0, std::move(grandparents_), is_manual_,
|
||||
start_level_score_, false /* deletion_compaction */, compaction_reason_);
|
||||
|
||||
|
||||
@@ -751,7 +751,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, start_level,
|
||||
1, enable_compression),
|
||||
GetCompressionOptions(ioptions_, vstorage_, start_level,
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, start_level,
|
||||
enable_compression),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */, compaction_reason);
|
||||
@@ -959,8 +959,8 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(ioptions_, vstorage_, output_level),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ true,
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
}
|
||||
@@ -1029,7 +1029,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToOldest(
|
||||
LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, start_level,
|
||||
1, true /* enable_compression */),
|
||||
GetCompressionOptions(ioptions_, vstorage_, start_level,
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, start_level,
|
||||
true /* enable_compression */),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */, compaction_reason);
|
||||
|
||||
@@ -190,7 +190,6 @@ class CorruptionTest : public testing::Test {
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
Options options;
|
||||
EnvOptions env_options;
|
||||
options.file_system.reset(new LegacyFileSystemWrapper(options.env));
|
||||
ASSERT_NOK(VerifySstFileChecksum(options, env_options, fname));
|
||||
}
|
||||
|
||||
|
||||
+237
-405
@@ -6,6 +6,7 @@
|
||||
// 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 "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
@@ -324,7 +325,12 @@ TEST_F(DBBasicTest, CheckLock) {
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// second open should fail
|
||||
ASSERT_TRUE(!(DB::Open(options, dbname_, &localdb)).ok());
|
||||
Status s = DB::Open(options, dbname_, &localdb);
|
||||
ASSERT_NOK(s);
|
||||
#ifdef OS_LINUX
|
||||
ASSERT_TRUE(s.ToString().find("lock hold by current process") !=
|
||||
std::string::npos);
|
||||
#endif // OS_LINUX
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
@@ -392,7 +398,7 @@ TEST_F(DBBasicTest, FlushEmptyColumnFamily) {
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, FLUSH) {
|
||||
TEST_F(DBBasicTest, Flush) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
WriteOptions writeOpt = WriteOptions();
|
||||
@@ -919,44 +925,44 @@ TEST_F(DBBasicTest, MmapAndBufferOptions) {
|
||||
#endif
|
||||
|
||||
class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit TestEnv(Env* base_env) : EnvWrapper(base_env), close_count(0) {}
|
||||
public:
|
||||
explicit TestEnv(Env* base_env) : EnvWrapper(base_env), close_count(0) {}
|
||||
|
||||
class TestLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
explicit TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; }
|
||||
~TestLogger() override {
|
||||
if (!closed_) {
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
void Logv(const char* /*format*/, va_list /*ap*/) override {}
|
||||
|
||||
protected:
|
||||
Status CloseImpl() override { return CloseHelper(); }
|
||||
|
||||
private:
|
||||
Status CloseHelper() {
|
||||
env->CloseCountInc();
|
||||
;
|
||||
return Status::IOError();
|
||||
}
|
||||
TestEnv* env;
|
||||
};
|
||||
|
||||
void CloseCountInc() { close_count++; }
|
||||
|
||||
int GetCloseCount() { return close_count; }
|
||||
|
||||
Status NewLogger(const std::string& /*fname*/,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
result->reset(new TestLogger(this));
|
||||
return Status::OK();
|
||||
class TestLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
explicit TestLogger(TestEnv* env_ptr) : Logger() { env = env_ptr; }
|
||||
~TestLogger() override {
|
||||
if (!closed_) {
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
void Logv(const char* /*format*/, va_list /*ap*/) override {}
|
||||
|
||||
protected:
|
||||
Status CloseImpl() override { return CloseHelper(); }
|
||||
|
||||
private:
|
||||
int close_count;
|
||||
Status CloseHelper() {
|
||||
env->CloseCountInc();
|
||||
;
|
||||
return Status::IOError();
|
||||
}
|
||||
TestEnv* env;
|
||||
};
|
||||
|
||||
void CloseCountInc() { close_count++; }
|
||||
|
||||
int GetCloseCount() { return close_count; }
|
||||
|
||||
Status NewLogger(const std::string& /*fname*/,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
result->reset(new TestLogger(this));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
int close_count;
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTest, DBClose) {
|
||||
@@ -1008,7 +1014,7 @@ TEST_F(DBBasicTest, DBCloseFlushError) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.manual_wal_flush = true;
|
||||
options.write_buffer_size=100;
|
||||
options.write_buffer_size = 100;
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
Reopen(options);
|
||||
@@ -1458,7 +1464,8 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevelMerge) {
|
||||
ASSERT_EQ(0, num_keys);
|
||||
|
||||
for (int i = 0; i < 128; i += 9) {
|
||||
ASSERT_OK(Merge("key_" + std::to_string(i), "val_mem_" + std::to_string(i)));
|
||||
ASSERT_OK(
|
||||
Merge("key_" + std::to_string(i), "val_mem_" + std::to_string(i)));
|
||||
}
|
||||
|
||||
std::vector<std::string> keys;
|
||||
@@ -1696,8 +1703,8 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.pin_l0_filter_and_index_blocks_in_cache = true;
|
||||
table_options.block_size = 16 * 1024;
|
||||
assert(table_options.block_size >
|
||||
BlockBasedTable::kMultiGetReadStackBufSize);
|
||||
ASSERT_TRUE(table_options.block_size >
|
||||
BlockBasedTable::kMultiGetReadStackBufSize);
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
@@ -1729,6 +1736,160 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IncrementalRecoveryNoCorrupt) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
WriteOptions write_opts;
|
||||
write_opts.disableWAL = true;
|
||||
for (size_t cf = 0; cf != num_cfs; ++cf) {
|
||||
for (size_t i = 0; i != 10000; ++i) {
|
||||
std::string key_str = Key(static_cast<int>(i));
|
||||
std::string value_str = std::to_string(cf) + "_" + std::to_string(i);
|
||||
|
||||
ASSERT_OK(Put(static_cast<int>(cf), key_str, value_str));
|
||||
if (0 == (i % 1000)) {
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t cf = 0; cf != num_cfs; ++cf) {
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
}
|
||||
Close();
|
||||
options.best_efforts_recovery = true;
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu", "eevee"},
|
||||
options);
|
||||
num_cfs = handles_.size();
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
for (size_t cf = 0; cf != num_cfs; ++cf) {
|
||||
for (int i = 0; i != 10000; ++i) {
|
||||
std::string key_str = Key(static_cast<int>(i));
|
||||
std::string expected_value_str =
|
||||
std::to_string(cf) + "_" + std::to_string(i);
|
||||
ASSERT_EQ(expected_value_str, Get(static_cast<int>(cf), key_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
class TableFileListener : public EventListener {
|
||||
public:
|
||||
void OnTableFileCreated(const TableFileCreationInfo& info) override {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
cf_to_paths_[info.cf_name].push_back(info.file_path);
|
||||
}
|
||||
std::vector<std::string>& GetFiles(const std::string& cf_name) {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
return cf_to_paths_[cf_name];
|
||||
}
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::unordered_map<std::string, std::vector<std::string>> cf_to_paths_;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBBasicTest, RecoverWithMissingFiles) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
TableFileListener* listener = new TableFileListener();
|
||||
// Disable auto compaction to simplify SST file name tracking.
|
||||
options.disable_auto_compactions = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
CreateAndReopenWithCF({"pikachu", "eevee"}, options);
|
||||
std::vector<std::string> all_cf_names = {kDefaultColumnFamilyName, "pikachu",
|
||||
"eevee"};
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(3, num_cfs);
|
||||
for (size_t cf = 0; cf != num_cfs; ++cf) {
|
||||
ASSERT_OK(Put(static_cast<int>(cf), "a", "0_value"));
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
ASSERT_OK(Put(static_cast<int>(cf), "b", "0_value"));
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
ASSERT_OK(Put(static_cast<int>(cf), "c", "0_value"));
|
||||
ASSERT_OK(Flush(static_cast<int>(cf)));
|
||||
}
|
||||
|
||||
// Delete files
|
||||
for (size_t i = 0; i < all_cf_names.size(); ++i) {
|
||||
std::vector<std::string>& files = listener->GetFiles(all_cf_names[i]);
|
||||
ASSERT_EQ(3, files.size());
|
||||
for (int j = static_cast<int>(files.size() - 1); j >= static_cast<int>(i);
|
||||
--j) {
|
||||
ASSERT_OK(env_->DeleteFile(files[j]));
|
||||
}
|
||||
}
|
||||
options.best_efforts_recovery = true;
|
||||
ReopenWithColumnFamilies(all_cf_names, options);
|
||||
// Verify data
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts, handles_[0]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
iter.reset(db_->NewIterator(read_opts, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
iter.reset(db_->NewIterator(read_opts, handles_[2]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("b", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, SkipWALIfMissingTableFiles) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
TableFileListener* listener = new TableFileListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
std::vector<std::string> kAllCfNames = {kDefaultColumnFamilyName, "pikachu"};
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
for (int cf = 0; cf < static_cast<int>(kAllCfNames.size()); ++cf) {
|
||||
ASSERT_OK(Put(cf, "a", "0_value"));
|
||||
ASSERT_OK(Flush(cf));
|
||||
ASSERT_OK(Put(cf, "b", "0_value"));
|
||||
}
|
||||
// Delete files
|
||||
for (size_t i = 0; i < kAllCfNames.size(); ++i) {
|
||||
std::vector<std::string>& files = listener->GetFiles(kAllCfNames[i]);
|
||||
ASSERT_EQ(1, files.size());
|
||||
for (int j = static_cast<int>(files.size() - 1); j >= static_cast<int>(i);
|
||||
--j) {
|
||||
ASSERT_OK(env_->DeleteFile(files[j]));
|
||||
}
|
||||
}
|
||||
options.best_efforts_recovery = true;
|
||||
ReopenWithColumnFamilies(kAllCfNames, options);
|
||||
// Verify WAL is not applied
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts, handles_[0]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
iter.reset(db_->NewIterator(read_opts, handles_[1]));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("a", iter->key());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class DBBasicTestWithParallelIO
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool, bool, bool>> {
|
||||
@@ -1760,10 +1921,17 @@ class DBBasicTestWithParallelIO
|
||||
compression_types = GetSupportedCompressions();
|
||||
// Not every platform may have compression libraries available, so
|
||||
// dynamically pick based on what's available
|
||||
if (compression_types.size() == 0) {
|
||||
compression_enabled_ = false;
|
||||
CompressionType tmp_type = kNoCompression;
|
||||
for (auto c_type : compression_types) {
|
||||
if (c_type != kNoCompression) {
|
||||
tmp_type = c_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tmp_type != kNoCompression) {
|
||||
options.compression = tmp_type;
|
||||
} else {
|
||||
options.compression = compression_types[0];
|
||||
compression_enabled_ = false;
|
||||
}
|
||||
}
|
||||
#else
|
||||
@@ -1771,7 +1939,7 @@ class DBBasicTestWithParallelIO
|
||||
if (!Snappy_Supported()) {
|
||||
compression_enabled_ = false;
|
||||
}
|
||||
#endif //ROCKSDB_LITE
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
table_options.block_cache = uncompressed_cache_;
|
||||
if (table_options.block_cache == nullptr) {
|
||||
@@ -2108,13 +2276,13 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) {
|
||||
ro.fill_cache = fill_cache();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RetrieveMultipleBlocks:VerifyChecksum", [&](void *status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
read_count++;
|
||||
if (read_count == 2) {
|
||||
*s = Status::Corruption();
|
||||
}
|
||||
});
|
||||
"RetrieveMultipleBlocks:VerifyChecksum", [&](void* status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
read_count++;
|
||||
if (read_count == 2) {
|
||||
*s = Status::Corruption();
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Warm up the cache first
|
||||
@@ -2127,7 +2295,7 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) {
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(CheckValue(0, values[0].ToString()));
|
||||
//ASSERT_TRUE(CheckValue(50, values[1].ToString()));
|
||||
// ASSERT_TRUE(CheckValue(50, values[1].ToString()));
|
||||
ASSERT_EQ(statuses[0], Status::OK());
|
||||
ASSERT_EQ(statuses[1], Status::Corruption());
|
||||
|
||||
@@ -2145,10 +2313,10 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithMissingFile) {
|
||||
ro.fill_cache = fill_cache();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"TableCache::MultiGet:FindTable", [&](void *status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
*s = Status::IOError();
|
||||
});
|
||||
"TableCache::MultiGet:FindTable", [&](void* status) {
|
||||
Status* s = static_cast<Status*>(status);
|
||||
*s = Status::IOError();
|
||||
});
|
||||
// DB open will create table readers unless we reduce the table cache
|
||||
// capacity.
|
||||
// SanitizeOptions will set max_open_files to minimum of 20. Table cache
|
||||
@@ -2157,10 +2325,10 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithMissingFile) {
|
||||
// prevent file open during DB open and force the file to be opened
|
||||
// during MultiGet
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void *arg) {
|
||||
int* max_open_files = (int*)arg;
|
||||
*max_open_files = 11;
|
||||
});
|
||||
"SanitizeOptions::AfterChangeMaxOpenFiles", [&](void* arg) {
|
||||
int* max_open_files = (int*)arg;
|
||||
*max_open_files = 11;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Reopen(CurrentOptions());
|
||||
@@ -2180,352 +2348,16 @@ TEST_P(DBBasicTestWithParallelIO, MultiGetWithMissingFile) {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ParallelIO, DBBasicTestWithParallelIO,
|
||||
// Params are as follows -
|
||||
// Param 0 - Compressed cache enabled
|
||||
// Param 1 - Uncompressed cache enabled
|
||||
// Param 2 - Data compression enabled
|
||||
// Param 3 - ReadOptions::fill_cache
|
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool(), ::testing::Bool()));
|
||||
INSTANTIATE_TEST_CASE_P(ParallelIO, DBBasicTestWithParallelIO,
|
||||
// Params are as follows -
|
||||
// Param 0 - Compressed cache enabled
|
||||
// Param 1 - Uncompressed cache enabled
|
||||
// Param 2 - Data compression enabled
|
||||
// Param 3 - ReadOptions::fill_cache
|
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool(),
|
||||
::testing::Bool()));
|
||||
|
||||
class DBBasicTestWithTimestampBase : public DBTestBase {
|
||||
public:
|
||||
explicit DBBasicTestWithTimestampBase(const std::string& dbname)
|
||||
: DBTestBase(dbname) {}
|
||||
|
||||
protected:
|
||||
class TestComparatorBase : public Comparator {
|
||||
public:
|
||||
explicit TestComparatorBase(size_t ts_sz) : Comparator(ts_sz) {}
|
||||
|
||||
const char* Name() const override { return "TestComparator"; }
|
||||
|
||||
void FindShortSuccessor(std::string*) const override {}
|
||||
|
||||
void FindShortestSeparator(std::string*, const Slice&) const override {}
|
||||
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
int r = CompareWithoutTimestamp(a, b);
|
||||
if (r != 0 || 0 == timestamp_size()) {
|
||||
return r;
|
||||
}
|
||||
return CompareTimestamp(
|
||||
Slice(a.data() + a.size() - timestamp_size(), timestamp_size()),
|
||||
Slice(b.data() + b.size() - timestamp_size(), timestamp_size()));
|
||||
}
|
||||
|
||||
virtual int CompareImpl(const Slice& a, const Slice& b) const = 0;
|
||||
|
||||
int CompareWithoutTimestamp(const Slice& a, const Slice& b) const override {
|
||||
assert(a.size() >= timestamp_size());
|
||||
assert(b.size() >= timestamp_size());
|
||||
Slice k1 = StripTimestampFromUserKey(a, timestamp_size());
|
||||
Slice k2 = StripTimestampFromUserKey(b, timestamp_size());
|
||||
|
||||
return CompareImpl(k1, k2);
|
||||
}
|
||||
|
||||
int CompareTimestamp(const Slice& ts1, const Slice& ts2) const override {
|
||||
if (!ts1.data() && !ts2.data()) {
|
||||
return 0;
|
||||
} else if (ts1.data() && !ts2.data()) {
|
||||
return 1;
|
||||
} else if (!ts1.data() && ts2.data()) {
|
||||
return -1;
|
||||
}
|
||||
assert(ts1.size() == ts2.size());
|
||||
uint64_t low1 = 0;
|
||||
uint64_t low2 = 0;
|
||||
uint64_t high1 = 0;
|
||||
uint64_t high2 = 0;
|
||||
auto* ptr1 = const_cast<Slice*>(&ts1);
|
||||
auto* ptr2 = const_cast<Slice*>(&ts2);
|
||||
if (!GetFixed64(ptr1, &low1) || !GetFixed64(ptr1, &high1) ||
|
||||
!GetFixed64(ptr2, &low2) || !GetFixed64(ptr2, &high2)) {
|
||||
assert(false);
|
||||
}
|
||||
if (high1 < high2) {
|
||||
return 1;
|
||||
} else if (high1 > high2) {
|
||||
return -1;
|
||||
}
|
||||
if (low1 < low2) {
|
||||
return 1;
|
||||
} else if (low1 > low2) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
Slice EncodeTimestamp(uint64_t low, uint64_t high, std::string* ts) {
|
||||
assert(nullptr != ts);
|
||||
ts->clear();
|
||||
PutFixed64(ts, low);
|
||||
PutFixed64(ts, high);
|
||||
assert(ts->size() == sizeof(low) + sizeof(high));
|
||||
return Slice(*ts);
|
||||
}
|
||||
};
|
||||
|
||||
class DBBasicTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
DBBasicTestWithTimestamp()
|
||||
: DBBasicTestWithTimestampBase("/db_basic_test_with_timestamp") {}
|
||||
|
||||
protected:
|
||||
class TestComparator : public TestComparatorBase {
|
||||
public:
|
||||
const int kKeyPrefixLength =
|
||||
3; // 3: length of "key" in generated keys ("key" + std::to_string(j))
|
||||
explicit TestComparator(size_t ts_sz) : TestComparatorBase(ts_sz) {}
|
||||
|
||||
int CompareImpl(const Slice& a, const Slice& b) const override {
|
||||
int n1 = atoi(
|
||||
std::string(a.data() + kKeyPrefixLength, a.size() - kKeyPrefixLength)
|
||||
.c_str());
|
||||
int n2 = atoi(
|
||||
std::string(b.data() + kKeyPrefixLength, b.size() - kKeyPrefixLength)
|
||||
.c_str());
|
||||
return (n1 < n2) ? -1 : (n1 > n2) ? 1 : 0;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// A class which remembers the name of each flushed file.
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() override {}
|
||||
|
||||
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFlushedFiles() {
|
||||
std::vector<std::string> result;
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
result = flushed_files_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearFlushedFiles() {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
flushed_files_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> flushed_files_;
|
||||
InstrumentedMutex mutex_;
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, PutAndGetWithCompaction) {
|
||||
const int kNumKeysPerFile = 8192;
|
||||
const size_t kNumTimestamps = 2;
|
||||
const size_t kNumKeysPerTimestamp = (kNumKeysPerFile - 1) / kNumTimestamps;
|
||||
const size_t kSplitPosBase = kNumKeysPerTimestamp / 2;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
|
||||
std::string tmp;
|
||||
size_t ts_sz = EncodeTimestamp(0, 0, &tmp).size();
|
||||
TestComparator test_cmp(ts_sz);
|
||||
options.comparator = &test_cmp;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(
|
||||
10 /*bits_per_key*/, false /*use_block_based_builder*/));
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
std::vector<std::string> write_ts_strs(kNumTimestamps);
|
||||
std::vector<std::string> read_ts_strs(kNumTimestamps);
|
||||
std::vector<Slice> write_ts_list;
|
||||
std::vector<Slice> read_ts_list;
|
||||
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
write_ts_list.emplace_back(EncodeTimestamp(i * 2, 0, &write_ts_strs[i]));
|
||||
read_ts_list.emplace_back(EncodeTimestamp(1 + i * 2, 0, &read_ts_strs[i]));
|
||||
const Slice& write_ts = write_ts_list.back();
|
||||
WriteOptions wopts;
|
||||
wopts.timestamp = &write_ts;
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
ASSERT_OK(Put(cf, "key" + std::to_string(j),
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
wopts));
|
||||
if (j == kSplitPosBase + i || j == kNumKeysPerTimestamp - 1) {
|
||||
// flush all keys with the same timestamp to two sst files, split at
|
||||
// incremental positions such that lowerlevel[1].smallest.userkey ==
|
||||
// higherlevel[0].largest.userkey
|
||||
ASSERT_OK(Flush(cf));
|
||||
|
||||
// compact files (2 at each level) to a lower level such that all keys
|
||||
// with the same timestamp is at one level, with newer versions at
|
||||
// higher levels.
|
||||
CompactionOptions compact_opt;
|
||||
compact_opt.compression = kNoCompression;
|
||||
db_->CompactFiles(compact_opt, handles_[cf],
|
||||
collector->GetFlushedFiles(),
|
||||
static_cast<int>(kNumTimestamps - i));
|
||||
collector->ClearFlushedFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& verify_db_func = [&]() {
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
ReadOptions ropts;
|
||||
ropts.timestamp = &read_ts_list[i];
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
ColumnFamilyHandle* cfh = handles_[cf];
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ropts, cfh, "key" + std::to_string(j), &value));
|
||||
ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
verify_db_func();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class DBBasicTestWithTimestampWithParam
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBBasicTestWithTimestampWithParam()
|
||||
: DBBasicTestWithTimestampBase(
|
||||
"/db_basic_test_with_timestamp_with_param") {}
|
||||
|
||||
protected:
|
||||
class TestComparator : public TestComparatorBase {
|
||||
private:
|
||||
const Comparator* cmp_without_ts_;
|
||||
|
||||
public:
|
||||
explicit TestComparator(size_t ts_sz)
|
||||
: TestComparatorBase(ts_sz), cmp_without_ts_(nullptr) {
|
||||
cmp_without_ts_ = BytewiseComparator();
|
||||
}
|
||||
|
||||
int CompareImpl(const Slice& a, const Slice& b) const override {
|
||||
return cmp_without_ts_->Compare(a, b);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampWithParam, PutAndGet) {
|
||||
const int kNumKeysPerFile = 8192;
|
||||
const size_t kNumTimestamps = 6;
|
||||
bool memtable_only = GetParam();
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
std::string tmp;
|
||||
size_t ts_sz = EncodeTimestamp(0, 0, &tmp).size();
|
||||
TestComparator test_cmp(ts_sz);
|
||||
options.comparator = &test_cmp;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(
|
||||
10 /*bits_per_key*/, false /*use_block_based_builder*/));
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
|
||||
std::vector<CompressionType> compression_types;
|
||||
compression_types.push_back(kNoCompression);
|
||||
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_Supported()) {
|
||||
compression_types.push_back(kZSTD);
|
||||
}
|
||||
|
||||
// Switch compression dictionary on/off to check key extraction
|
||||
// correctness in kBuffered state
|
||||
std::vector<uint32_t> max_dict_bytes_list = {0, 1 << 14}; // 0 or 16KB
|
||||
|
||||
for (auto compression_type : compression_types) {
|
||||
for (uint32_t max_dict_bytes : max_dict_bytes_list) {
|
||||
options.compression = compression_type;
|
||||
options.compression_opts.max_dict_bytes = max_dict_bytes;
|
||||
if (compression_type == kZSTD) {
|
||||
options.compression_opts.zstd_max_train_bytes = max_dict_bytes;
|
||||
}
|
||||
options.target_file_size_base = 1 << 26; // 64MB
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
std::vector<std::string> write_ts_strs(kNumTimestamps);
|
||||
std::vector<std::string> read_ts_strs(kNumTimestamps);
|
||||
std::vector<Slice> write_ts_list;
|
||||
std::vector<Slice> read_ts_list;
|
||||
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
write_ts_list.emplace_back(
|
||||
EncodeTimestamp(i * 2, 0, &write_ts_strs[i]));
|
||||
read_ts_list.emplace_back(
|
||||
EncodeTimestamp(1 + i * 2, 0, &read_ts_strs[i]));
|
||||
const Slice& write_ts = write_ts_list.back();
|
||||
WriteOptions wopts;
|
||||
wopts.timestamp = &write_ts;
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
for (size_t j = 0; j != (kNumKeysPerFile - 1) / kNumTimestamps; ++j) {
|
||||
ASSERT_OK(Put(
|
||||
cf, "key" + std::to_string(j),
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i), wopts));
|
||||
}
|
||||
if (!memtable_only) {
|
||||
ASSERT_OK(Flush(cf));
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& verify_db_func = [&]() {
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
ReadOptions ropts;
|
||||
ropts.timestamp = &read_ts_list[i];
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
ColumnFamilyHandle* cfh = handles_[cf];
|
||||
for (size_t j = 0; j != (kNumKeysPerFile - 1) / kNumTimestamps;
|
||||
++j) {
|
||||
std::string value;
|
||||
ASSERT_OK(
|
||||
db_->Get(ropts, cfh, "key" + std::to_string(j), &value));
|
||||
ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
verify_db_func();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Timestamp, DBBasicTestWithTimestampWithParam,
|
||||
::testing::Bool());
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -593,6 +593,72 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeBottomPri) {
|
||||
ASSERT_OK(Put(Key(50), ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(100), ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(200), ""));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
{
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
}
|
||||
ASSERT_EQ("0,0,3", FilesPerLevel(0));
|
||||
|
||||
ASSERT_OK(Put(Key(1), ""));
|
||||
ASSERT_OK(Put(Key(199), ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Key(2), ""));
|
||||
ASSERT_OK(Put(Key(199), ""));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("2,0,3", FilesPerLevel(0));
|
||||
|
||||
// Now we have 2 L0 files, and 3 L2 files, and a manual compaction will
|
||||
// be triggered.
|
||||
// Two compaction jobs will run. One compacts 2 L0 files in Low Pri Pool
|
||||
// and one compact to L2 in bottom pri pool.
|
||||
int low_pri_count = 0;
|
||||
int bottom_pri_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ThreadPoolImpl::Impl::BGThread:BeforeRun", [&](void* arg) {
|
||||
Env::Priority* pri = reinterpret_cast<Env::Priority*>(arg);
|
||||
// First time is low pri pool in the test case.
|
||||
if (low_pri_count == 0 && bottom_pri_count == 0) {
|
||||
ASSERT_EQ(Env::Priority::LOW, *pri);
|
||||
}
|
||||
if (*pri == Env::Priority::LOW) {
|
||||
low_pri_count++;
|
||||
} else {
|
||||
bottom_pri_count++;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
env_->SetBackgroundThreads(1, Env::Priority::BOTTOM);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(1, low_pri_count);
|
||||
ASSERT_EQ(1, bottom_pri_count);
|
||||
ASSERT_EQ("0,0,2", FilesPerLevel(0));
|
||||
|
||||
// Recompact bottom most level uses bottom pool
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
ASSERT_EQ(1, low_pri_count);
|
||||
ASSERT_EQ(2, bottom_pri_count);
|
||||
|
||||
env_->SetBackgroundThreads(0, Env::Priority::BOTTOM);
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
// Low pri pool is used if bottom pool has size 0.
|
||||
ASSERT_EQ(2, low_pri_count);
|
||||
ASSERT_EQ(2, bottom_pri_count);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
uint64_t db_size[3];
|
||||
for (int test = 0; test < 2; ++test) {
|
||||
@@ -2459,7 +2525,7 @@ TEST_P(DBCompactionTestWithParam, ManualCompaction) {
|
||||
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
||||
|
||||
// Compaction range overlaps files
|
||||
Compact(1, "p1", "p9");
|
||||
Compact(1, "p", "q");
|
||||
ASSERT_EQ("0,0,1", FilesPerLevel(1));
|
||||
|
||||
// Populate a different range
|
||||
@@ -2526,7 +2592,7 @@ TEST_P(DBCompactionTestWithParam, ManualLevelCompactionOutputPathId) {
|
||||
ASSERT_EQ("3", FilesPerLevel(1));
|
||||
|
||||
// Compaction range overlaps files
|
||||
Compact(1, "p1", "p9", 1);
|
||||
Compact(1, "p", "q", 1);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ("0,1", FilesPerLevel(1));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
@@ -4655,7 +4721,7 @@ TEST_P(DBCompactionDirectIOTest, DirectIO) {
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
MakeTables(3, "p", "q", 1);
|
||||
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
||||
Compact(1, "p1", "p9");
|
||||
Compact(1, "p", "q");
|
||||
ASSERT_EQ(readahead, options.use_direct_reads);
|
||||
ASSERT_EQ("0,0,1", FilesPerLevel(1));
|
||||
Destroy(options);
|
||||
|
||||
+114
-65
@@ -21,7 +21,6 @@
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -151,7 +150,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
own_info_log_(options.info_log == nullptr),
|
||||
initial_db_options_(SanitizeOptions(dbname, options)),
|
||||
env_(initial_db_options_.env),
|
||||
fs_(initial_db_options_.file_system),
|
||||
fs_(initial_db_options_.env->GetFileSystem()),
|
||||
immutable_db_options_(initial_db_options_),
|
||||
mutable_db_options_(initial_db_options_),
|
||||
stats_(immutable_db_options_.statistics.get()),
|
||||
@@ -312,6 +311,11 @@ Status DBImpl::ResumeImpl() {
|
||||
s = bg_error;
|
||||
}
|
||||
|
||||
// Make sure the IO Status stored in version set is set to OK.
|
||||
if (s.ok()) {
|
||||
versions_->SetIOStatusOK();
|
||||
}
|
||||
|
||||
// We cannot guarantee consistency of the WAL. So force flush Memtables of
|
||||
// all the column families
|
||||
if (s.ok()) {
|
||||
@@ -890,9 +894,9 @@ void DBImpl::ScheduleBgLogWriterClose(JobContext* job_context) {
|
||||
}
|
||||
}
|
||||
|
||||
Directory* DBImpl::GetDataDir(ColumnFamilyData* cfd, size_t path_id) const {
|
||||
FSDirectory* DBImpl::GetDataDir(ColumnFamilyData* cfd, size_t path_id) const {
|
||||
assert(cfd);
|
||||
Directory* ret_dir = cfd->GetDataDir(path_id);
|
||||
FSDirectory* ret_dir = cfd->GetDataDir(path_id);
|
||||
if (ret_dir == nullptr) {
|
||||
return directories_.GetDataDir(path_id);
|
||||
}
|
||||
@@ -1147,25 +1151,25 @@ int DBImpl::FindMinimumEmptyLevelFitting(
|
||||
|
||||
Status DBImpl::FlushWAL(bool sync) {
|
||||
if (manual_wal_flush_) {
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
{
|
||||
// We need to lock log_write_mutex_ since logs_ might change concurrently
|
||||
InstrumentedMutexLock wl(&log_write_mutex_);
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
s = cur_log_writer->WriteBuffer();
|
||||
io_s = cur_log_writer->WriteBuffer();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL flush error %s",
|
||||
s.ToString().c_str());
|
||||
io_s.ToString().c_str());
|
||||
// In case there is a fs error we should set it globally to prevent the
|
||||
// future writes
|
||||
WriteStatusCheck(s);
|
||||
IOStatusCheck(io_s);
|
||||
// whether sync or not, we should abort the rest of function upon error
|
||||
return s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
if (!sync) {
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "FlushWAL sync=false");
|
||||
return s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
}
|
||||
if (!sync) {
|
||||
@@ -1217,14 +1221,23 @@ Status DBImpl::SyncWAL() {
|
||||
TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:1");
|
||||
RecordTick(stats_, WAL_FILE_SYNCED);
|
||||
Status status;
|
||||
IOStatus io_s;
|
||||
for (log::Writer* log : logs_to_sync) {
|
||||
status = log->file()->SyncWithoutFlush(immutable_db_options_.use_fsync);
|
||||
if (!status.ok()) {
|
||||
io_s = log->file()->SyncWithoutFlush(immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
status = io_s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log, "WAL Sync error %s",
|
||||
io_s.ToString().c_str());
|
||||
// In case there is a fs error we should set it globally to prevent the
|
||||
// future writes
|
||||
IOStatusCheck(io_s);
|
||||
}
|
||||
if (status.ok() && need_log_dir_sync) {
|
||||
status = directories_.GetWalDir()->Fsync();
|
||||
status = directories_.GetWalDir()->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
TEST_SYNC_POINT("DBWALTest::SyncWALNotWaitWrite:2");
|
||||
|
||||
@@ -1249,7 +1262,7 @@ Status DBImpl::LockWAL() {
|
||||
// future writes
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
return status;
|
||||
return std::move(status);
|
||||
}
|
||||
|
||||
Status DBImpl::UnlockWAL() {
|
||||
@@ -1496,14 +1509,22 @@ ColumnFamilyHandle* DBImpl::PersistentStatsColumnFamily() const {
|
||||
Status DBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) {
|
||||
return Get(read_options, column_family, key, value, /*timestamp=*/nullptr);
|
||||
}
|
||||
|
||||
Status DBImpl::Get(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) {
|
||||
GetImplOptions get_impl_options;
|
||||
get_impl_options.column_family = column_family;
|
||||
get_impl_options.value = value;
|
||||
return GetImpl(read_options, key, get_impl_options);
|
||||
get_impl_options.timestamp = timestamp;
|
||||
Status s = GetImpl(read_options, key, get_impl_options);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
GetImplOptions get_impl_options) {
|
||||
GetImplOptions& get_impl_options) {
|
||||
assert(get_impl_options.value != nullptr ||
|
||||
get_impl_options.merge_operands != nullptr);
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
@@ -1583,10 +1604,14 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
bool skip_memtable = (read_options.read_tier == kPersistedTier &&
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed));
|
||||
bool done = false;
|
||||
const Comparator* comparator =
|
||||
get_impl_options.column_family->GetComparator();
|
||||
size_t ts_sz = comparator->timestamp_size();
|
||||
std::string* timestamp = ts_sz > 0 ? get_impl_options.timestamp : nullptr;
|
||||
if (!skip_memtable) {
|
||||
// Get value associated with key
|
||||
if (get_impl_options.get_value) {
|
||||
if (sv->mem->Get(lkey, get_impl_options.value->GetSelf(), &s,
|
||||
if (sv->mem->Get(lkey, get_impl_options.value->GetSelf(), timestamp, &s,
|
||||
&merge_context, &max_covering_tombstone_seq,
|
||||
read_options, get_impl_options.callback,
|
||||
get_impl_options.is_blob_index)) {
|
||||
@@ -1594,9 +1619,10 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
get_impl_options.value->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
sv->imm->Get(lkey, get_impl_options.value->GetSelf(), &s,
|
||||
&merge_context, &max_covering_tombstone_seq,
|
||||
read_options, get_impl_options.callback,
|
||||
sv->imm->Get(lkey, get_impl_options.value->GetSelf(),
|
||||
timestamp, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options,
|
||||
get_impl_options.callback,
|
||||
get_impl_options.is_blob_index)) {
|
||||
done = true;
|
||||
get_impl_options.value->PinSelf();
|
||||
@@ -1605,9 +1631,9 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
} else {
|
||||
// Get Merge Operands associated with key, Merge Operands should not be
|
||||
// merged and raw values should be returned to the user.
|
||||
if (sv->mem->Get(lkey, nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options, nullptr,
|
||||
nullptr, false)) {
|
||||
if (sv->mem->Get(lkey, /*value*/ nullptr, /*timestamp=*/nullptr, &s,
|
||||
&merge_context, &max_covering_tombstone_seq,
|
||||
read_options, nullptr, nullptr, false)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
@@ -1626,8 +1652,8 @@ Status DBImpl::GetImpl(const ReadOptions& read_options, const Slice& key,
|
||||
if (!done) {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
sv->current->Get(
|
||||
read_options, lkey, get_impl_options.value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq,
|
||||
read_options, lkey, get_impl_options.value, timestamp, &s,
|
||||
&merge_context, &max_covering_tombstone_seq,
|
||||
get_impl_options.get_value ? get_impl_options.value_found : nullptr,
|
||||
nullptr, nullptr,
|
||||
get_impl_options.get_value ? get_impl_options.callback : nullptr,
|
||||
@@ -1675,12 +1701,20 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values) {
|
||||
return MultiGet(read_options, column_family, keys, values,
|
||||
/*timestamps*/ nullptr);
|
||||
}
|
||||
|
||||
std::vector<Status> DBImpl::MultiGet(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) {
|
||||
PERF_CPU_TIMER_GUARD(get_cpu_nanos, env_);
|
||||
StopWatch sw(env_, stats_, DB_MULTIGET);
|
||||
PERF_TIMER_GUARD(get_snapshot_time);
|
||||
|
||||
SequenceNumber consistent_seqnum;
|
||||
;
|
||||
|
||||
std::unordered_map<uint32_t, MultiGetColumnFamilyData> multiget_cf_data(
|
||||
column_family.size());
|
||||
@@ -1711,6 +1745,9 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
size_t num_keys = keys.size();
|
||||
std::vector<Status> stat_list(num_keys);
|
||||
values->resize(num_keys);
|
||||
if (timestamps) {
|
||||
timestamps->resize(num_keys);
|
||||
}
|
||||
|
||||
// Keep track of bytes that we read for statistics-recording later
|
||||
uint64_t bytes_read = 0;
|
||||
@@ -1725,8 +1762,9 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
merge_context.Clear();
|
||||
Status& s = stat_list[i];
|
||||
std::string* value = &(*values)[i];
|
||||
std::string* timestamp = timestamps ? &(*timestamps)[i] : nullptr;
|
||||
|
||||
LookupKey lkey(keys[i], consistent_seqnum);
|
||||
LookupKey lkey(keys[i], consistent_seqnum, read_options.timestamp);
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family[i]);
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
auto mgd_iter = multiget_cf_data.find(cfh->cfd()->GetID());
|
||||
@@ -1738,13 +1776,13 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
has_unpersisted_data_.load(std::memory_order_relaxed));
|
||||
bool done = false;
|
||||
if (!skip_memtable) {
|
||||
if (super_version->mem->Get(lkey, value, &s, &merge_context,
|
||||
if (super_version->mem->Get(lkey, value, timestamp, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if (super_version->imm->Get(lkey, value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq,
|
||||
read_options)) {
|
||||
} else if (super_version->imm->Get(
|
||||
lkey, value, timestamp, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
}
|
||||
@@ -1752,8 +1790,9 @@ std::vector<Status> DBImpl::MultiGet(
|
||||
if (!done) {
|
||||
PinnableSlice pinnable_val;
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, &pinnable_val, &s,
|
||||
&merge_context, &max_covering_tombstone_seq);
|
||||
super_version->current->Get(read_options, lkey, &pinnable_val, timestamp,
|
||||
&s, &merge_context,
|
||||
&max_covering_tombstone_seq);
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
@@ -1915,6 +1954,14 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
return MultiGet(read_options, num_keys, column_families, keys, values,
|
||||
/*timestamps*/ nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses, const bool sorted_input) {
|
||||
if (num_keys == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -1923,7 +1970,7 @@ void DBImpl::MultiGet(const ReadOptions& read_options, const size_t num_keys,
|
||||
sorted_keys.resize(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
key_context.emplace_back(column_families[i], keys[i], &values[i],
|
||||
&statuses[i]);
|
||||
×tamps[i], &statuses[i]);
|
||||
}
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
sorted_keys[i] = &key_context[i];
|
||||
@@ -2043,11 +2090,22 @@ void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
Status* statuses, const bool sorted_input) {
|
||||
return MultiGet(read_options, column_family, num_keys, keys, values,
|
||||
/*timestamp=*/nullptr, statuses, sorted_input);
|
||||
}
|
||||
|
||||
void DBImpl::MultiGet(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const size_t num_keys,
|
||||
const Slice* keys, PinnableSlice* values,
|
||||
std::string* timestamps, Status* statuses,
|
||||
const bool sorted_input) {
|
||||
autovector<KeyContext, MultiGetContext::MAX_BATCH_SIZE> key_context;
|
||||
autovector<KeyContext*, MultiGetContext::MAX_BATCH_SIZE> sorted_keys;
|
||||
sorted_keys.resize(num_keys);
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
key_context.emplace_back(column_family, keys[i], &values[i], &statuses[i]);
|
||||
key_context.emplace_back(column_family, keys[i], &values[i],
|
||||
timestamps ? ×tamps[i] : nullptr,
|
||||
&statuses[i]);
|
||||
}
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
sorted_keys[i] = &key_context[i];
|
||||
@@ -2301,7 +2359,7 @@ Status DBImpl::CreateColumnFamilyImpl(const ColumnFamilyOptions& cf_options,
|
||||
auto* cfd =
|
||||
versions_->GetColumnFamilySet()->GetColumnFamily(column_family_name);
|
||||
assert(cfd != nullptr);
|
||||
std::map<std::string, std::shared_ptr<Directory>> dummy_created_dirs;
|
||||
std::map<std::string, std::shared_ptr<FSDirectory>> dummy_created_dirs;
|
||||
s = cfd->AddDirectories(&dummy_created_dirs);
|
||||
}
|
||||
if (s.ok()) {
|
||||
@@ -2436,7 +2494,8 @@ Status DBImpl::DropColumnFamilyImpl(ColumnFamilyHandle* column_family) {
|
||||
|
||||
bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value, bool* value_found) {
|
||||
std::string* value, std::string* timestamp,
|
||||
bool* value_found) {
|
||||
assert(value != nullptr);
|
||||
if (value_found != nullptr) {
|
||||
// falsify later if key-may-exist but can't fetch value
|
||||
@@ -2449,6 +2508,7 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
get_impl_options.column_family = column_family;
|
||||
get_impl_options.value = &pinnable_val;
|
||||
get_impl_options.value_found = value_found;
|
||||
get_impl_options.timestamp = timestamp;
|
||||
auto s = GetImpl(roptions, key, get_impl_options);
|
||||
value->assign(pinnable_val.data(), pinnable_val.size());
|
||||
|
||||
@@ -3462,12 +3522,8 @@ Status DBImpl::Close() {
|
||||
Status DB::ListColumnFamilies(const DBOptions& db_options,
|
||||
const std::string& name,
|
||||
std::vector<std::string>* column_families) {
|
||||
FileSystem* fs = db_options.file_system.get();
|
||||
LegacyFileSystemWrapper legacy_fs(db_options.env);
|
||||
if (!fs) {
|
||||
fs = &legacy_fs;
|
||||
}
|
||||
return VersionSet::ListColumnFamilies(column_families, name, fs);
|
||||
const std::shared_ptr<FileSystem>& fs = db_options.env->GetFileSystem();
|
||||
return VersionSet::ListColumnFamilies(column_families, name, fs.get());
|
||||
}
|
||||
|
||||
Snapshot::~Snapshot() {}
|
||||
@@ -3511,24 +3567,15 @@ Status DestroyDB(const std::string& dbname, const Options& options,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> paths;
|
||||
|
||||
for (const auto& path : options.db_paths) {
|
||||
paths.emplace_back(path.path);
|
||||
std::set<std::string> paths;
|
||||
for (const DbPath& db_path : options.db_paths) {
|
||||
paths.insert(db_path.path);
|
||||
}
|
||||
for (const auto& cf : column_families) {
|
||||
for (const auto& path : cf.options.cf_paths) {
|
||||
paths.emplace_back(path.path);
|
||||
for (const ColumnFamilyDescriptor& cf : column_families) {
|
||||
for (const DbPath& cf_path : cf.options.cf_paths) {
|
||||
paths.insert(cf_path.path);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate paths.
|
||||
// Note that we compare only the actual paths but not path ids.
|
||||
// This reason is that same path can appear at different path_ids
|
||||
// for different column families.
|
||||
std::sort(paths.begin(), paths.end());
|
||||
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
|
||||
|
||||
for (const auto& path : paths) {
|
||||
if (env->GetChildren(path, &filenames).ok()) {
|
||||
for (const auto& fname : filenames) {
|
||||
@@ -3819,8 +3866,9 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
*found_record_for_key = false;
|
||||
|
||||
// Check if there is a record for this key in the latest memtable
|
||||
sv->mem->Get(lkey, nullptr, &s, &merge_context, &max_covering_tombstone_seq,
|
||||
seq, read_options, nullptr /*read_callback*/, is_blob_index);
|
||||
sv->mem->Get(lkey, nullptr, nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, seq, read_options,
|
||||
nullptr /*read_callback*/, is_blob_index);
|
||||
|
||||
if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
|
||||
// unexpected error reading memtable.
|
||||
@@ -3845,8 +3893,9 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
}
|
||||
|
||||
// Check if there is a record for this key in the immutable memtables
|
||||
sv->imm->Get(lkey, nullptr, &s, &merge_context, &max_covering_tombstone_seq,
|
||||
seq, read_options, nullptr /*read_callback*/, is_blob_index);
|
||||
sv->imm->Get(lkey, nullptr, nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, seq, read_options,
|
||||
nullptr /*read_callback*/, is_blob_index);
|
||||
|
||||
if (!(s.ok() || s.IsNotFound() || s.IsMergeInProgress())) {
|
||||
// unexpected error reading memtable.
|
||||
@@ -3871,7 +3920,7 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
}
|
||||
|
||||
// Check if there is a record for this key in the immutable memtables
|
||||
sv->imm->GetFromHistory(lkey, nullptr, &s, &merge_context,
|
||||
sv->imm->GetFromHistory(lkey, nullptr, nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, seq, read_options,
|
||||
is_blob_index);
|
||||
|
||||
@@ -3899,7 +3948,7 @@ Status DBImpl::GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
// SST files if cache_only=true?
|
||||
if (!cache_only) {
|
||||
// Check tables
|
||||
sv->current->Get(read_options, lkey, nullptr, &s, &merge_context,
|
||||
sv->current->Get(read_options, lkey, nullptr, nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, nullptr /* value_found */,
|
||||
found_record_for_key, seq, nullptr /*read_callback*/,
|
||||
is_blob_index);
|
||||
|
||||
+62
-26
@@ -82,13 +82,13 @@ struct MemTableInfo;
|
||||
// Class to maintain directories for all database paths other than main one.
|
||||
class Directories {
|
||||
public:
|
||||
Status SetDirectories(Env* env, const std::string& dbname,
|
||||
const std::string& wal_dir,
|
||||
const std::vector<DbPath>& data_paths);
|
||||
IOStatus SetDirectories(FileSystem* fs, const std::string& dbname,
|
||||
const std::string& wal_dir,
|
||||
const std::vector<DbPath>& data_paths);
|
||||
|
||||
Directory* GetDataDir(size_t path_id) const {
|
||||
FSDirectory* GetDataDir(size_t path_id) const {
|
||||
assert(path_id < data_dirs_.size());
|
||||
Directory* ret_dir = data_dirs_[path_id].get();
|
||||
FSDirectory* ret_dir = data_dirs_[path_id].get();
|
||||
if (ret_dir == nullptr) {
|
||||
// Should use db_dir_
|
||||
return db_dir_.get();
|
||||
@@ -96,19 +96,19 @@ class Directories {
|
||||
return ret_dir;
|
||||
}
|
||||
|
||||
Directory* GetWalDir() {
|
||||
FSDirectory* GetWalDir() {
|
||||
if (wal_dir_) {
|
||||
return wal_dir_.get();
|
||||
}
|
||||
return db_dir_.get();
|
||||
}
|
||||
|
||||
Directory* GetDbDir() { return db_dir_.get(); }
|
||||
FSDirectory* GetDbDir() { return db_dir_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<Directory> db_dir_;
|
||||
std::vector<std::unique_ptr<Directory>> data_dirs_;
|
||||
std::unique_ptr<Directory> wal_dir_;
|
||||
std::unique_ptr<FSDirectory> db_dir_;
|
||||
std::vector<std::unique_ptr<FSDirectory>> data_dirs_;
|
||||
std::unique_ptr<FSDirectory> wal_dir_;
|
||||
};
|
||||
|
||||
// While DB is the public interface of RocksDB, and DBImpl is the actual
|
||||
@@ -163,6 +163,9 @@ class DBImpl : public DB {
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value, std::string* timestamp) override;
|
||||
|
||||
using DB::GetMergeOperands;
|
||||
Status GetMergeOperands(const ReadOptions& options,
|
||||
@@ -185,6 +188,11 @@ class DBImpl : public DB {
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const std::vector<Slice>& keys, std::vector<std::string>* values,
|
||||
std::vector<std::string>* timestamps) override;
|
||||
|
||||
// This MultiGet is a batched version, which may be faster than calling Get
|
||||
// multiple times, especially if the keys have some spatial locality that
|
||||
@@ -198,11 +206,22 @@ class DBImpl : public DB {
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
virtual void MultiGet(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const size_t num_keys, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
|
||||
virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
virtual void MultiGet(const ReadOptions& options, const size_t num_keys,
|
||||
ColumnFamilyHandle** column_families, const Slice* keys,
|
||||
PinnableSlice* values, std::string* timestamps,
|
||||
Status* statuses,
|
||||
const bool sorted_input = false) override;
|
||||
|
||||
virtual void MultiGetWithCallback(
|
||||
const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
@@ -230,7 +249,7 @@ class DBImpl : public DB {
|
||||
using DB::KeyMayExist;
|
||||
virtual bool KeyMayExist(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value,
|
||||
std::string* value, std::string* timestamp,
|
||||
bool* value_found = nullptr) override;
|
||||
|
||||
using DB::NewIterator;
|
||||
@@ -433,6 +452,7 @@ class DBImpl : public DB {
|
||||
struct GetImplOptions {
|
||||
ColumnFamilyHandle* column_family = nullptr;
|
||||
PinnableSlice* value = nullptr;
|
||||
std::string* timestamp = nullptr;
|
||||
bool* value_found = nullptr;
|
||||
ReadCallback* callback = nullptr;
|
||||
bool* is_blob_index = nullptr;
|
||||
@@ -455,7 +475,7 @@ class DBImpl : public DB {
|
||||
// If get_impl_options.get_value = false get merge operands associated with
|
||||
// get_impl_options.key via get_impl_options.merge_operands
|
||||
Status GetImpl(const ReadOptions& options, const Slice& key,
|
||||
GetImplOptions get_impl_options);
|
||||
GetImplOptions& get_impl_options);
|
||||
|
||||
ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& options,
|
||||
ColumnFamilyData* cfd,
|
||||
@@ -826,8 +846,9 @@ class DBImpl : public DB {
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
const bool seq_per_batch, const bool batch_per_txn);
|
||||
|
||||
static Status CreateAndNewDirectory(Env* env, const std::string& dirname,
|
||||
std::unique_ptr<Directory>* directory);
|
||||
static IOStatus CreateAndNewDirectory(
|
||||
FileSystem* fs, const std::string& dirname,
|
||||
std::unique_ptr<FSDirectory>* directory);
|
||||
|
||||
// find stats map from stats_history_ with smallest timestamp in
|
||||
// the range of [start_time, end_time)
|
||||
@@ -1119,6 +1140,17 @@ class DBImpl : public DB {
|
||||
|
||||
virtual bool OwnTablesAndLogs() const { return true; }
|
||||
|
||||
// REQUIRES: db mutex held when calling this function, but the db mutex can
|
||||
// be released and re-acquired. Db mutex will be held when the function
|
||||
// returns.
|
||||
// Currently, this function should be called only in best-efforts recovery
|
||||
// mode.
|
||||
// After best-efforts recovery, there may be SST files in db/cf paths that are
|
||||
// not referenced in the MANIFEST. We delete these SST files. In the
|
||||
// meantime, we find out the largest file number present in the paths, and
|
||||
// bump up the version set's next_file_number_ to be 1 + largest_file_number.
|
||||
Status CleanupFilesAfterRecovery();
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class ErrorHandler;
|
||||
@@ -1332,7 +1364,7 @@ class DBImpl : public DB {
|
||||
void ReleaseFileNumberFromPendingOutputs(
|
||||
std::unique_ptr<std::list<uint64_t>::iterator>& v);
|
||||
|
||||
Status SyncClosedLogs(JobContext* job_context);
|
||||
IOStatus SyncClosedLogs(JobContext* job_context);
|
||||
|
||||
// Flush the in-memory write buffer to storage. Switches to a new
|
||||
// log-file/memtable and writes a new descriptor iff successful. Then
|
||||
@@ -1469,21 +1501,25 @@ class DBImpl : public DB {
|
||||
WriteBatch* tmp_batch, size_t* write_with_wal,
|
||||
WriteBatch** to_be_cached_state);
|
||||
|
||||
Status WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer,
|
||||
uint64_t* log_used, uint64_t* log_size);
|
||||
IOStatus WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer,
|
||||
uint64_t* log_used, uint64_t* log_size);
|
||||
|
||||
Status WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence);
|
||||
IOStatus WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence);
|
||||
|
||||
Status ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used, SequenceNumber* last_sequence,
|
||||
size_t seq_inc);
|
||||
IOStatus ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used,
|
||||
SequenceNumber* last_sequence, size_t seq_inc);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ if paranoid check is enabled.
|
||||
void WriteStatusCheck(const Status& status);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ when IO error happens, e.g., write
|
||||
// WAL, sync WAL fails, if paranoid check is enabled.
|
||||
void IOStatusCheck(const IOStatus& status);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ in case of memtable insert error.
|
||||
void MemTableInsertStatusCheck(const Status& memtable_insert_status);
|
||||
|
||||
@@ -1598,7 +1634,7 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t GetMaxTotalWalSize() const;
|
||||
|
||||
Directory* GetDataDir(ColumnFamilyData* cfd, size_t path_id) const;
|
||||
FSDirectory* GetDataDir(ColumnFamilyData* cfd, size_t path_id) const;
|
||||
|
||||
Status CloseHelper();
|
||||
|
||||
@@ -2021,7 +2057,7 @@ class DBImpl : public DB {
|
||||
// REQUIRES: mutex locked
|
||||
std::unique_ptr<ROCKSDB_NAMESPACE::RepeatableThread> thread_persist_stats_;
|
||||
|
||||
// When set, we use a separate queue for writes that dont write to memtable.
|
||||
// When set, we use a separate queue for writes that don't write to memtable.
|
||||
// In 2PC these are the writes at Prepare phase.
|
||||
const bool two_write_queues_;
|
||||
const bool manual_wal_flush_;
|
||||
|
||||
@@ -79,7 +79,7 @@ bool DBImpl::RequestCompactionToken(ColumnFamilyData* cfd, bool force,
|
||||
return false;
|
||||
}
|
||||
|
||||
Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
IOStatus DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
|
||||
mutex_.AssertHeld();
|
||||
autovector<log::Writer*, 1> logs_to_sync;
|
||||
@@ -96,7 +96,7 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
logs_to_sync.push_back(log.writer);
|
||||
}
|
||||
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
if (!logs_to_sync.empty()) {
|
||||
mutex_.Unlock();
|
||||
|
||||
@@ -104,34 +104,34 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[JOB %d] Syncing log #%" PRIu64, job_context->job_id,
|
||||
log->get_log_number());
|
||||
s = log->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!s.ok()) {
|
||||
io_s = log->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (immutable_db_options_.recycle_log_file_num > 0) {
|
||||
s = log->Close();
|
||||
if (!s.ok()) {
|
||||
io_s = log->Close();
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = directories_.GetWalDir()->Fsync();
|
||||
if (io_s.ok()) {
|
||||
io_s = directories_.GetWalDir()->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
|
||||
mutex_.Lock();
|
||||
|
||||
// "number <= current_log_number - 1" is equivalent to
|
||||
// "number < current_log_number".
|
||||
MarkLogsSynced(current_log_number - 1, true, s);
|
||||
if (!s.ok()) {
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
MarkLogsSynced(current_log_number - 1, true, io_s);
|
||||
if (!io_s.ok()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Failed");
|
||||
return s;
|
||||
return io_s;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
return io_s;
|
||||
}
|
||||
|
||||
Status DBImpl::FlushMemTableToOutputFile(
|
||||
@@ -155,7 +155,6 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats,
|
||||
true /* sync_output_directory */, true /* write_manifest */, thread_pri);
|
||||
|
||||
FileMetaData file_meta;
|
||||
|
||||
TEST_SYNC_POINT("DBImpl::FlushMemTableToOutputFile:BeforePickMemtables");
|
||||
@@ -168,6 +167,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
if (logfile_number_ > 0 &&
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() > 1) {
|
||||
// If there are more than one column families, we need to make sure that
|
||||
@@ -176,7 +176,8 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
// flushed SST may contain data from write batches whose updates to
|
||||
// other column families are missing.
|
||||
// SyncClosedLogs() may unlock and re-lock the db_mutex.
|
||||
s = SyncClosedLogs(job_context);
|
||||
io_s = SyncClosedLogs(job_context);
|
||||
s = io_s;
|
||||
} else {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Skip");
|
||||
}
|
||||
@@ -192,6 +193,7 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
} else {
|
||||
flush_job.Cancel();
|
||||
}
|
||||
io_s = flush_job.io_status();
|
||||
|
||||
if (s.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, superversion_context,
|
||||
@@ -206,8 +208,13 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
}
|
||||
|
||||
if (!s.ok() && !s.IsShutdownInProgress() && !s.IsColumnFamilyDropped()) {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
if (!io_s.ok() && !io_s.IsShutdownInProgress() &&
|
||||
!io_s.IsColumnFamilyDropped()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
} else {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -301,7 +308,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
GetSnapshotContext(job_context, &snapshot_seqs,
|
||||
&earliest_write_conflict_snapshot, &snapshot_checker);
|
||||
|
||||
autovector<Directory*> distinct_output_dirs;
|
||||
autovector<FSDirectory*> distinct_output_dirs;
|
||||
autovector<std::string> distinct_output_dir_paths;
|
||||
std::vector<std::unique_ptr<FlushJob>> jobs;
|
||||
std::vector<MutableCFOptions> all_mutable_cf_options;
|
||||
@@ -309,7 +316,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
all_mutable_cf_options.reserve(num_cfs);
|
||||
for (int i = 0; i < num_cfs; ++i) {
|
||||
auto cfd = cfds[i];
|
||||
Directory* data_dir = GetDataDir(cfd, 0U);
|
||||
FSDirectory* data_dir = GetDataDir(cfd, 0U);
|
||||
const std::string& curr_path = cfd->ioptions()->cf_paths[0].path;
|
||||
|
||||
// Add to distinct output directories if eligible. Use linear search. Since
|
||||
@@ -344,6 +351,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
|
||||
std::vector<FileMetaData> file_meta(num_cfs);
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
assert(num_cfs == static_cast<int>(jobs.size()));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -358,15 +366,18 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
if (logfile_number_ > 0) {
|
||||
// TODO (yanqin) investigate whether we should sync the closed logs for
|
||||
// single column family case.
|
||||
s = SyncClosedLogs(job_context);
|
||||
io_s = SyncClosedLogs(job_context);
|
||||
s = io_s;
|
||||
}
|
||||
|
||||
// exec_status stores the execution status of flush_jobs as
|
||||
// <bool /* executed */, Status /* status code */>
|
||||
autovector<std::pair<bool, Status>> exec_status;
|
||||
autovector<IOStatus> io_status;
|
||||
for (int i = 0; i != num_cfs; ++i) {
|
||||
// Initially all jobs are not executed, with status OK.
|
||||
exec_status.emplace_back(false, Status::OK());
|
||||
io_status.emplace_back(IOStatus::OK());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
@@ -375,6 +386,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
exec_status[i].second =
|
||||
jobs[i]->Run(&logs_with_prep_tracker_, &file_meta[i]);
|
||||
exec_status[i].first = true;
|
||||
io_status[i] = jobs[i]->io_status();
|
||||
}
|
||||
if (num_cfs > 1) {
|
||||
TEST_SYNC_POINT(
|
||||
@@ -387,6 +399,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
exec_status[0].second =
|
||||
jobs[0]->Run(&logs_with_prep_tracker_, &file_meta[0]);
|
||||
exec_status[0].first = true;
|
||||
io_status[0] = jobs[0]->io_status();
|
||||
|
||||
Status error_status;
|
||||
for (const auto& e : exec_status) {
|
||||
@@ -405,6 +418,20 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
s = error_status.ok() ? s : error_status;
|
||||
}
|
||||
|
||||
if (io_s.ok()) {
|
||||
IOStatus io_error = IOStatus::OK();
|
||||
for (int i = 0; i != static_cast<int>(io_status.size()); i++) {
|
||||
if (!io_status[i].ok() && !io_status[i].IsShutdownInProgress() &&
|
||||
!io_status[i].IsColumnFamilyDropped()) {
|
||||
io_error = io_status[i];
|
||||
}
|
||||
}
|
||||
io_s = io_error;
|
||||
if (s.ok() && !io_s.ok()) {
|
||||
s = io_s;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.IsColumnFamilyDropped()) {
|
||||
s = Status::OK();
|
||||
}
|
||||
@@ -413,7 +440,7 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
// Sync on all distinct output directories.
|
||||
for (auto dir : distinct_output_dirs) {
|
||||
if (dir != nullptr) {
|
||||
Status error_status = dir->Fsync();
|
||||
Status error_status = dir->Fsync(IOOptions(), nullptr);
|
||||
if (!error_status.ok()) {
|
||||
s = error_status;
|
||||
break;
|
||||
@@ -543,9 +570,15 @@ Status DBImpl::AtomicFlushMemTablesToOutputFiles(
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
// Need to undo atomic flush if something went wrong, i.e. s is not OK and
|
||||
// it is not because of CF drop.
|
||||
if (!s.ok() && !s.IsColumnFamilyDropped()) {
|
||||
if (!io_s.ok() && io_s.IsColumnFamilyDropped()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kFlush);
|
||||
} else {
|
||||
Status new_bg_error = s;
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -652,8 +685,6 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
return Status::InvalidArgument("Invalid target path ID");
|
||||
}
|
||||
|
||||
bool exclusive = options.exclusive_manual_compaction;
|
||||
|
||||
bool flush_needed = true;
|
||||
if (begin != nullptr && end != nullptr) {
|
||||
// TODO(ajkr): We could also optimize away the flush in certain cases where
|
||||
@@ -686,25 +717,9 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
}
|
||||
}
|
||||
|
||||
int max_level_with_files = 0;
|
||||
// max_file_num_to_ignore can be used to filter out newly created SST files,
|
||||
// useful for bottom level compaction in a manual compaction
|
||||
uint64_t max_file_num_to_ignore = port::kMaxUint64;
|
||||
uint64_t next_file_number = port::kMaxUint64;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
Version* base = cfd->current();
|
||||
for (int level = 1; level < base->storage_info()->num_non_empty_levels();
|
||||
level++) {
|
||||
if (base->storage_info()->OverlapInLevel(level, begin, end)) {
|
||||
max_level_with_files = level;
|
||||
}
|
||||
}
|
||||
next_file_number = versions_->current_next_file_number();
|
||||
}
|
||||
|
||||
int final_output_level = 0;
|
||||
|
||||
constexpr int kInvalidLevel = -1;
|
||||
int final_output_level = kInvalidLevel;
|
||||
bool exclusive = options.exclusive_manual_compaction;
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal &&
|
||||
cfd->NumberLevels() > 1) {
|
||||
// Always compact all files together.
|
||||
@@ -715,58 +730,98 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
}
|
||||
s = RunManualCompaction(cfd, ColumnFamilyData::kCompactAllLevels,
|
||||
final_output_level, options, begin, end, exclusive,
|
||||
false, max_file_num_to_ignore);
|
||||
false, port::kMaxUint64);
|
||||
} else {
|
||||
for (int level = 0; level <= max_level_with_files; level++) {
|
||||
int first_overlapped_level = kInvalidLevel;
|
||||
int max_overlapped_level = kInvalidLevel;
|
||||
{
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(this);
|
||||
Version* current_version = super_version->current;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
bool overlap;
|
||||
for (int level = 0;
|
||||
level < current_version->storage_info()->num_non_empty_levels();
|
||||
level++) {
|
||||
overlap = true;
|
||||
if (begin != nullptr && end != nullptr) {
|
||||
Status status = current_version->OverlapWithLevelIterator(
|
||||
ro, file_options_, *begin, *end, level, &overlap);
|
||||
if (!status.ok()) {
|
||||
overlap = current_version->storage_info()->OverlapInLevel(
|
||||
level, begin, end);
|
||||
}
|
||||
} else {
|
||||
overlap = current_version->storage_info()->OverlapInLevel(level,
|
||||
begin, end);
|
||||
}
|
||||
if (overlap) {
|
||||
if (first_overlapped_level == kInvalidLevel) {
|
||||
first_overlapped_level = level;
|
||||
}
|
||||
max_overlapped_level = level;
|
||||
}
|
||||
}
|
||||
CleanupSuperVersion(super_version);
|
||||
}
|
||||
if (s.ok() && first_overlapped_level != kInvalidLevel) {
|
||||
// max_file_num_to_ignore can be used to filter out newly created SST
|
||||
// files, useful for bottom level compaction in a manual compaction
|
||||
uint64_t max_file_num_to_ignore = port::kMaxUint64;
|
||||
uint64_t next_file_number = versions_->current_next_file_number();
|
||||
final_output_level = max_overlapped_level;
|
||||
int output_level;
|
||||
// in case the compaction is universal or if we're compacting the
|
||||
// bottom-most level, the output level will be the same as input one.
|
||||
// level 0 can never be the bottommost level (i.e. if all files are in
|
||||
// level 0, we will compact to level 1)
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal ||
|
||||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
|
||||
output_level = level;
|
||||
} else if (level == max_level_with_files && level > 0) {
|
||||
if (options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kSkip) {
|
||||
// Skip bottommost level compaction
|
||||
continue;
|
||||
} else if (options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter &&
|
||||
cfd->ioptions()->compaction_filter == nullptr &&
|
||||
cfd->ioptions()->compaction_filter_factory == nullptr) {
|
||||
// Skip bottommost level compaction since we don't have a compaction
|
||||
// filter
|
||||
continue;
|
||||
for (int level = first_overlapped_level; level <= max_overlapped_level;
|
||||
level++) {
|
||||
// in case the compaction is universal or if we're compacting the
|
||||
// bottom-most level, the output level will be the same as input one.
|
||||
// level 0 can never be the bottommost level (i.e. if all files are in
|
||||
// level 0, we will compact to level 1)
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleUniversal ||
|
||||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
|
||||
output_level = level;
|
||||
} else if (level == max_overlapped_level && level > 0) {
|
||||
if (options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kSkip) {
|
||||
// Skip bottommost level compaction
|
||||
continue;
|
||||
} else if (options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter &&
|
||||
cfd->ioptions()->compaction_filter == nullptr &&
|
||||
cfd->ioptions()->compaction_filter_factory == nullptr) {
|
||||
// Skip bottommost level compaction since we don't have a compaction
|
||||
// filter
|
||||
continue;
|
||||
}
|
||||
output_level = level;
|
||||
// update max_file_num_to_ignore only for bottom level compaction
|
||||
// because data in newly compacted files in middle levels may still
|
||||
// need to be pushed down
|
||||
max_file_num_to_ignore = next_file_number;
|
||||
} else {
|
||||
output_level = level + 1;
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleLevel &&
|
||||
cfd->ioptions()->level_compaction_dynamic_level_bytes &&
|
||||
level == 0) {
|
||||
output_level = ColumnFamilyData::kCompactToBaseLevel;
|
||||
}
|
||||
}
|
||||
output_level = level;
|
||||
// update max_file_num_to_ignore only for bottom level compaction
|
||||
// because data in newly compacted files in middle levels may still need
|
||||
// to be pushed down
|
||||
max_file_num_to_ignore = next_file_number;
|
||||
} else {
|
||||
output_level = level + 1;
|
||||
if (cfd->ioptions()->compaction_style == kCompactionStyleLevel &&
|
||||
cfd->ioptions()->level_compaction_dynamic_level_bytes &&
|
||||
level == 0) {
|
||||
output_level = ColumnFamilyData::kCompactToBaseLevel;
|
||||
s = RunManualCompaction(cfd, level, output_level, options, begin, end,
|
||||
exclusive, false, max_file_num_to_ignore);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
if (output_level == ColumnFamilyData::kCompactToBaseLevel) {
|
||||
final_output_level = cfd->NumberLevels() - 1;
|
||||
} else if (output_level > final_output_level) {
|
||||
final_output_level = output_level;
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::RunManualCompaction()::1");
|
||||
TEST_SYNC_POINT("DBImpl::RunManualCompaction()::2");
|
||||
}
|
||||
s = RunManualCompaction(cfd, level, output_level, options, begin, end,
|
||||
exclusive, false, max_file_num_to_ignore);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
if (output_level == ColumnFamilyData::kCompactToBaseLevel) {
|
||||
final_output_level = cfd->NumberLevels() - 1;
|
||||
} else if (output_level > final_output_level) {
|
||||
final_output_level = output_level;
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::RunManualCompaction()::1");
|
||||
TEST_SYNC_POINT("DBImpl::RunManualCompaction()::2");
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
if (!s.ok() || final_output_level == kInvalidLevel) {
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return s;
|
||||
}
|
||||
@@ -1037,7 +1092,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
}
|
||||
|
||||
if (output_file_names != nullptr) {
|
||||
for (const auto newf : c->edit()->GetNewFiles()) {
|
||||
for (const auto& newf : c->edit()->GetNewFiles()) {
|
||||
(*output_file_names)
|
||||
.push_back(TableFileName(c->immutable_cf_options()->cf_paths,
|
||||
newf.second.fd.GetNumber(),
|
||||
@@ -1137,7 +1192,7 @@ void DBImpl::NotifyOnCompactionBegin(ColumnFamilyData* cfd, Compaction* c,
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto newf : c->edit()->GetNewFiles()) {
|
||||
for (const auto& newf : c->edit()->GetNewFiles()) {
|
||||
const FileMetaData& meta = newf.second;
|
||||
const FileDescriptor& desc = meta.fd;
|
||||
const uint64_t file_number = desc.GetNumber();
|
||||
@@ -1488,7 +1543,12 @@ Status DBImpl::RunManualCompaction(
|
||||
}
|
||||
manual.incomplete = false;
|
||||
bg_compaction_scheduled_++;
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, Env::Priority::LOW, this,
|
||||
Env::Priority thread_pool_pri = Env::Priority::LOW;
|
||||
if (compaction->bottommost_level() &&
|
||||
env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
|
||||
thread_pool_pri = Env::Priority::BOTTOM;
|
||||
}
|
||||
env_->Schedule(&DBImpl::BGWorkCompaction, ca, thread_pool_pri, this,
|
||||
&DBImpl::UnscheduleCompactionCallback);
|
||||
scheduled = true;
|
||||
}
|
||||
@@ -2606,6 +2666,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
}
|
||||
}
|
||||
|
||||
IOStatus io_s;
|
||||
if (!c) {
|
||||
// Nothing to do
|
||||
ROCKS_LOG_BUFFER(log_buffer, "Compaction nothing to do");
|
||||
@@ -2627,9 +2688,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
for (const auto& f : *c->inputs(0)) {
|
||||
c->edit()->DeleteFile(c->level(), f->fd.GetNumber());
|
||||
}
|
||||
versions_->SetIOStatusOK();
|
||||
status = versions_->LogAndApply(c->column_family_data(),
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
io_s = versions_->io_status();
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
*c->mutable_cf_options());
|
||||
@@ -2683,9 +2746,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
}
|
||||
}
|
||||
|
||||
versions_->SetIOStatusOK();
|
||||
status = versions_->LogAndApply(c->column_family_data(),
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
io_s = versions_->io_status();
|
||||
// Use latest MutableCFOptions
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
@@ -2772,6 +2837,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
mutex_.Lock();
|
||||
|
||||
status = compaction_job.Install(*c->mutable_cf_options());
|
||||
io_s = compaction_job.io_status();
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(c->column_family_data(),
|
||||
&job_context->superversion_contexts[0],
|
||||
@@ -2781,6 +2847,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:AfterCompaction",
|
||||
c->column_family_data());
|
||||
}
|
||||
|
||||
if (status.ok() && !io_s.ok()) {
|
||||
status = io_s;
|
||||
}
|
||||
|
||||
if (c != nullptr) {
|
||||
c->ReleaseCompactionFiles(status);
|
||||
*made_progress = true;
|
||||
@@ -2806,7 +2877,11 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log, "Compaction error: %s",
|
||||
status.ToString().c_str());
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
if (!io_s.ok()) {
|
||||
error_handler_.SetBGError(io_s, BackgroundErrorReason::kCompaction);
|
||||
} else {
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
if (c != nullptr && !is_manual && !error_handler_.IsBGWorkStopped()) {
|
||||
// Put this cfd back in the compaction queue so we can retry after some
|
||||
// time
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
@@ -664,4 +665,55 @@ uint64_t PrecomputeMinLogNumberToKeep(
|
||||
return min_log_number_to_keep;
|
||||
}
|
||||
|
||||
Status DBImpl::CleanupFilesAfterRecovery() {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<std::string> paths;
|
||||
paths.push_back(dbname_);
|
||||
for (const auto& db_path : immutable_db_options_.db_paths) {
|
||||
paths.push_back(db_path.path);
|
||||
}
|
||||
for (const auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
for (const auto& cf_path : cfd->ioptions()->cf_paths) {
|
||||
paths.push_back(cf_path.path);
|
||||
}
|
||||
}
|
||||
// Dedup paths
|
||||
std::sort(paths.begin(), paths.end());
|
||||
paths.erase(std::unique(paths.begin(), paths.end()), paths.end());
|
||||
|
||||
uint64_t next_file_number = versions_->current_next_file_number();
|
||||
uint64_t largest_file_number = next_file_number;
|
||||
std::set<std::string> files_to_delete;
|
||||
for (const auto& path : paths) {
|
||||
std::vector<std::string> files;
|
||||
env_->GetChildren(path, &files);
|
||||
for (const auto& fname : files) {
|
||||
uint64_t number = 0;
|
||||
FileType type;
|
||||
if (!ParseFileName(fname, &number, &type)) {
|
||||
continue;
|
||||
}
|
||||
const std::string normalized_fpath = NormalizePath(path + fname);
|
||||
largest_file_number = std::max(largest_file_number, number);
|
||||
if (type == kTableFile && number >= next_file_number &&
|
||||
files_to_delete.find(normalized_fpath) == files_to_delete.end()) {
|
||||
files_to_delete.insert(normalized_fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (largest_file_number > next_file_number) {
|
||||
versions_->next_file_number_.store(largest_file_number + 1);
|
||||
}
|
||||
mutex_.Unlock();
|
||||
Status s;
|
||||
for (const auto& fname : files_to_delete) {
|
||||
s = env_->DeleteFile(fname);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+52
-44
@@ -35,16 +35,8 @@ Options SanitizeOptions(const std::string& dbname, const Options& src) {
|
||||
DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
DBOptions result(src);
|
||||
|
||||
if (result.file_system == nullptr) {
|
||||
if (result.env == Env::Default()) {
|
||||
result.file_system = FileSystem::Default();
|
||||
} else {
|
||||
result.file_system.reset(new LegacyFileSystemWrapper(result.env));
|
||||
}
|
||||
} else {
|
||||
if (result.env == nullptr) {
|
||||
result.env = Env::Default();
|
||||
}
|
||||
if (result.env == nullptr) {
|
||||
result.env = Env::Default();
|
||||
}
|
||||
|
||||
// result.max_open_files means an "infinite" open files.
|
||||
@@ -252,6 +244,12 @@ Status DBImpl::ValidateOptions(const DBOptions& db_options) {
|
||||
"atomic_flush is incompatible with enable_pipelined_write");
|
||||
}
|
||||
|
||||
// TODO remove this restriction
|
||||
if (db_options.atomic_flush && db_options.best_efforts_recovery) {
|
||||
return Status::InvalidArgument(
|
||||
"atomic_flush is currently incompatible with best-efforts recovery");
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -294,15 +292,16 @@ Status DBImpl::NewDB() {
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(env_, dbname_, 1, directories_.GetDbDir());
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, directories_.GetDbDir());
|
||||
} else {
|
||||
fs_->DeleteFile(manifest, IOOptions(), nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::CreateAndNewDirectory(Env* env, const std::string& dirname,
|
||||
std::unique_ptr<Directory>* directory) {
|
||||
IOStatus DBImpl::CreateAndNewDirectory(
|
||||
FileSystem* fs, const std::string& dirname,
|
||||
std::unique_ptr<FSDirectory>* directory) {
|
||||
// We call CreateDirIfMissing() as the directory may already exist (if we
|
||||
// are reopening a DB), when this happens we don't want creating the
|
||||
// directory to cause an error. However, we need to check if creating the
|
||||
@@ -310,24 +309,24 @@ Status DBImpl::CreateAndNewDirectory(Env* env, const std::string& dirname,
|
||||
// file not existing. One real-world example of this occurring is if
|
||||
// env->CreateDirIfMissing() doesn't create intermediate directories, e.g.
|
||||
// when dbname_ is "dir/db" but when "dir" doesn't exist.
|
||||
Status s = env->CreateDirIfMissing(dirname);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
IOStatus io_s = fs->CreateDirIfMissing(dirname, IOOptions(), nullptr);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
return env->NewDirectory(dirname, directory);
|
||||
return fs->NewDirectory(dirname, IOOptions(), directory, nullptr);
|
||||
}
|
||||
|
||||
Status Directories::SetDirectories(Env* env, const std::string& dbname,
|
||||
const std::string& wal_dir,
|
||||
const std::vector<DbPath>& data_paths) {
|
||||
Status s = DBImpl::CreateAndNewDirectory(env, dbname, &db_dir_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
IOStatus Directories::SetDirectories(FileSystem* fs, const std::string& dbname,
|
||||
const std::string& wal_dir,
|
||||
const std::vector<DbPath>& data_paths) {
|
||||
IOStatus io_s = DBImpl::CreateAndNewDirectory(fs, dbname, &db_dir_);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
if (!wal_dir.empty() && dbname != wal_dir) {
|
||||
s = DBImpl::CreateAndNewDirectory(env, wal_dir, &wal_dir_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
io_s = DBImpl::CreateAndNewDirectory(fs, wal_dir, &wal_dir_);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -337,16 +336,16 @@ Status Directories::SetDirectories(Env* env, const std::string& dbname,
|
||||
if (db_path == dbname) {
|
||||
data_dirs_.emplace_back(nullptr);
|
||||
} else {
|
||||
std::unique_ptr<Directory> path_directory;
|
||||
s = DBImpl::CreateAndNewDirectory(env, db_path, &path_directory);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
std::unique_ptr<FSDirectory> path_directory;
|
||||
io_s = DBImpl::CreateAndNewDirectory(fs, db_path, &path_directory);
|
||||
if (!io_s.ok()) {
|
||||
return io_s;
|
||||
}
|
||||
data_dirs_.emplace_back(path_directory.release());
|
||||
}
|
||||
}
|
||||
assert(data_dirs_.size() == data_paths.size());
|
||||
return Status::OK();
|
||||
return IOStatus::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::Recover(
|
||||
@@ -358,7 +357,7 @@ Status DBImpl::Recover(
|
||||
bool is_new_db = false;
|
||||
assert(db_lock_ == nullptr);
|
||||
if (!read_only) {
|
||||
Status s = directories_.SetDirectories(env_, dbname_,
|
||||
Status s = directories_.SetDirectories(fs_.get(), dbname_,
|
||||
immutable_db_options_.wal_dir,
|
||||
immutable_db_options_.db_paths);
|
||||
if (!s.ok()) {
|
||||
@@ -418,7 +417,17 @@ Status DBImpl::Recover(
|
||||
}
|
||||
}
|
||||
assert(db_id_.empty());
|
||||
Status s = versions_->Recover(column_families, read_only, &db_id_);
|
||||
Status s;
|
||||
bool missing_table_file = false;
|
||||
if (!immutable_db_options_.best_efforts_recovery) {
|
||||
s = versions_->Recover(column_families, read_only, &db_id_);
|
||||
} else {
|
||||
s = versions_->TryRecover(column_families, read_only, &db_id_,
|
||||
&missing_table_file);
|
||||
if (s.ok()) {
|
||||
s = CleanupFilesAfterRecovery();
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -458,7 +467,7 @@ Status DBImpl::Recover(
|
||||
s = CheckConsistency();
|
||||
}
|
||||
if (s.ok() && !read_only) {
|
||||
std::map<std::string, std::shared_ptr<Directory>> created_dirs;
|
||||
std::map<std::string, std::shared_ptr<FSDirectory>> created_dirs;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
s = cfd->AddDirectories(&created_dirs);
|
||||
if (!s.ok()) {
|
||||
@@ -498,7 +507,9 @@ Status DBImpl::Recover(
|
||||
// attention to it in case we are recovering a database
|
||||
// produced by an older version of rocksdb.
|
||||
std::vector<std::string> filenames;
|
||||
s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
|
||||
if (!immutable_db_options_.best_efforts_recovery) {
|
||||
s = env_->GetChildren(immutable_db_options_.wal_dir, &filenames);
|
||||
}
|
||||
if (s.IsNotFound()) {
|
||||
return Status::InvalidArgument("wal_dir not found",
|
||||
immutable_db_options_.wal_dir);
|
||||
@@ -1228,6 +1239,7 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
if (range_del_iter != nullptr) {
|
||||
range_del_iters.emplace_back(range_del_iter);
|
||||
}
|
||||
IOStatus io_s;
|
||||
s = BuildTable(
|
||||
dbname_, env_, fs_.get(), *cfd->ioptions(), mutable_cf_options,
|
||||
file_options_for_compaction_, cfd->table_cache(), iter.get(),
|
||||
@@ -1236,8 +1248,8 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
mutable_cf_options.sample_for_compression,
|
||||
cfd->ioptions()->compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery,
|
||||
mutable_cf_options.compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery, &io_s,
|
||||
&event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
-1 /* level */, current_time, write_hint);
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
@@ -1400,13 +1412,9 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
impl->error_handler_.EnableAutoRecovery();
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
delete impl;
|
||||
return s;
|
||||
if (s.ok()) {
|
||||
s = impl->CreateArchivalDirectory();
|
||||
}
|
||||
|
||||
s = impl->CreateArchivalDirectory();
|
||||
if (!s.ok()) {
|
||||
delete impl;
|
||||
return s;
|
||||
@@ -1477,7 +1485,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
|
||||
impl->DeleteObsoleteFiles();
|
||||
s = impl->directories_.GetDbDir()->Fsync();
|
||||
s = impl->directories_.GetDbDir()->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
if (s.ok()) {
|
||||
// In WritePrepared there could be gap in sequence numbers. This breaks
|
||||
|
||||
@@ -48,14 +48,16 @@ Status DBImplReadOnly::Get(const ReadOptions& read_options,
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
LookupKey lkey(key, snapshot);
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context,
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(),
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, pinnable_val, &s,
|
||||
&merge_context, &max_covering_tombstone_seq);
|
||||
super_version->current->Get(read_options, lkey, pinnable_val,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
RecordTick(stats_, NUMBER_KEYS_READ);
|
||||
|
||||
@@ -340,15 +340,16 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
PERF_TIMER_STOP(get_snapshot_time);
|
||||
|
||||
bool done = false;
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(), &s, &merge_context,
|
||||
if (super_version->mem->Get(lkey, pinnable_val->GetSelf(),
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
} else if ((s.ok() || s.IsMergeInProgress()) &&
|
||||
super_version->imm->Get(
|
||||
lkey, pinnable_val->GetSelf(), &s, &merge_context,
|
||||
&max_covering_tombstone_seq, read_options)) {
|
||||
lkey, pinnable_val->GetSelf(), /*timestamp=*/nullptr, &s,
|
||||
&merge_context, &max_covering_tombstone_seq, read_options)) {
|
||||
done = true;
|
||||
pinnable_val->PinSelf();
|
||||
RecordTick(stats_, MEMTABLE_HIT);
|
||||
@@ -359,8 +360,9 @@ Status DBImplSecondary::GetImpl(const ReadOptions& read_options,
|
||||
}
|
||||
if (!done) {
|
||||
PERF_TIMER_GUARD(get_from_output_files_time);
|
||||
super_version->current->Get(read_options, lkey, pinnable_val, &s,
|
||||
&merge_context, &max_covering_tombstone_seq);
|
||||
super_version->current->Get(read_options, lkey, pinnable_val,
|
||||
/*timestamp=*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq);
|
||||
RecordTick(stats_, MEMTABLE_MISS);
|
||||
}
|
||||
{
|
||||
|
||||
+66
-38
@@ -101,6 +101,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
disable_memtable);
|
||||
|
||||
Status status;
|
||||
IOStatus io_s;
|
||||
if (write_options.low_pri) {
|
||||
status = ThrottleLowPriWritesIfNeeded(write_options, my_batch);
|
||||
if (!status.ok()) {
|
||||
@@ -322,21 +323,22 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (!two_write_queues_) {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
status = WriteToWAL(write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, last_sequence + 1);
|
||||
io_s = WriteToWAL(write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, last_sequence + 1);
|
||||
}
|
||||
} else {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
// LastAllocatedSequence is increased inside WriteToWAL under
|
||||
// wal_write_mutex_ to ensure ordered events in WAL
|
||||
status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
|
||||
seq_inc);
|
||||
io_s = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
|
||||
seq_inc);
|
||||
} else {
|
||||
// Otherwise we inc seq number for memtable writes
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
}
|
||||
}
|
||||
status = io_s;
|
||||
assert(last_sequence != kMaxSequenceNumber);
|
||||
const SequenceNumber current_sequence = last_sequence + 1;
|
||||
last_sequence += seq_inc;
|
||||
@@ -411,7 +413,11 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteStatusCheck(status);
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
@@ -515,6 +521,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
IOStatus io_s;
|
||||
if (w.status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
stats->AddDBStats(InternalStats::kIntStatsWriteDoneBySelf, 1);
|
||||
@@ -524,12 +531,17 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
wal_write_group.size - 1);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER, wal_write_group.size - 1);
|
||||
}
|
||||
w.status = WriteToWAL(wal_write_group, log_writer, log_used,
|
||||
need_log_sync, need_log_dir_sync, current_sequence);
|
||||
io_s = WriteToWAL(wal_write_group, log_writer, log_used, need_log_sync,
|
||||
need_log_dir_sync, current_sequence);
|
||||
w.status = io_s;
|
||||
}
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteStatusCheck(w.status);
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(w.status);
|
||||
}
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
@@ -740,9 +752,10 @@ Status DBImpl::WriteImplWALOnly(
|
||||
}
|
||||
seq_inc = total_batch_cnt;
|
||||
}
|
||||
IOStatus io_s;
|
||||
if (!write_options.disableWAL) {
|
||||
status =
|
||||
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
io_s = ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
status = io_s;
|
||||
} else {
|
||||
// Otherwise we inc seq number to do solely the seq allocation
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
@@ -777,7 +790,11 @@ Status DBImpl::WriteImplWALOnly(
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteStatusCheck(status);
|
||||
if (!io_s.ok()) {
|
||||
IOStatusCheck(io_s);
|
||||
} else {
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
size_t index = 0;
|
||||
@@ -823,6 +840,17 @@ void DBImpl::WriteStatusCheck(const Status& status) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::IOStatusCheck(const IOStatus& io_status) {
|
||||
// Is setting bg_error_ enough here? This will at least stop
|
||||
// compaction and fail any further writes.
|
||||
if (immutable_db_options_.paranoid_checks && !io_status.ok() &&
|
||||
!io_status.IsBusy() && !io_status.IsIncomplete()) {
|
||||
mutex_.Lock();
|
||||
error_handler_.SetBGError(io_status, BackgroundErrorReason::kWriteCallback);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::MemTableInsertStatusCheck(const Status& status) {
|
||||
// A non-OK status here indicates that the state implied by the
|
||||
// WAL has diverged from the in-memory state. This could be
|
||||
@@ -961,9 +989,9 @@ WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
|
||||
// When two_write_queues_ is disabled, this function is called from the only
|
||||
// write thread. Otherwise this must be called holding log_write_mutex_.
|
||||
Status DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
uint64_t* log_size) {
|
||||
IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
uint64_t* log_size) {
|
||||
assert(log_size != nullptr);
|
||||
Slice log_entry = WriteBatchInternal::Contents(&merged_batch);
|
||||
*log_size = log_entry.size();
|
||||
@@ -978,7 +1006,8 @@ Status DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
Status status = log_writer->AddRecord(log_entry);
|
||||
IOStatus io_s = log_writer->AddRecord(log_entry);
|
||||
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
@@ -990,15 +1019,14 @@ Status DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
// since alive_log_files_ might be modified concurrently
|
||||
alive_log_files_.back().AddSize(log_entry.size());
|
||||
log_empty_ = false;
|
||||
return status;
|
||||
return io_s;
|
||||
}
|
||||
|
||||
Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence) {
|
||||
Status status;
|
||||
|
||||
IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence) {
|
||||
IOStatus io_s;
|
||||
assert(!write_group.leader->disable_wal);
|
||||
// Same holds for all in the batch group
|
||||
size_t write_with_wal = 0;
|
||||
@@ -1016,13 +1044,13 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
WriteBatchInternal::SetSequence(merged_batch, sequence);
|
||||
|
||||
uint64_t log_size;
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
io_s = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
|
||||
if (status.ok() && need_log_sync) {
|
||||
if (io_s.ok() && need_log_sync) {
|
||||
StopWatch sw(env_, stats_, WAL_FILE_SYNC_MICROS);
|
||||
// It's safe to access logs_ with unlocked mutex_ here because:
|
||||
// - we've set getting_synced=true for all logs,
|
||||
@@ -1032,23 +1060,24 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
// - as long as other threads don't modify it, it's safe to read
|
||||
// from std::deque from multiple threads concurrently.
|
||||
for (auto& log : logs_) {
|
||||
status = log.writer->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!status.ok()) {
|
||||
io_s = log.writer->file()->Sync(immutable_db_options_.use_fsync);
|
||||
if (!io_s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (status.ok() && need_log_dir_sync) {
|
||||
|
||||
if (io_s.ok() && need_log_dir_sync) {
|
||||
// We only sync WAL directory the first time WAL syncing is
|
||||
// requested, so that in case users never turn on WAL sync,
|
||||
// we can avoid the disk I/O in the write code path.
|
||||
status = directories_.GetWalDir()->Fsync();
|
||||
io_s = directories_.GetWalDir()->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
if (merged_batch == &tmp_batch_) {
|
||||
tmp_batch_.Clear();
|
||||
}
|
||||
if (status.ok()) {
|
||||
if (io_s.ok()) {
|
||||
auto stats = default_cf_internal_stats_;
|
||||
if (need_log_sync) {
|
||||
stats->AddDBStats(InternalStats::kIntStatsWalFileSynced, 1);
|
||||
@@ -1059,14 +1088,13 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
stats->AddDBStats(InternalStats::kIntStatsWriteWithWal, write_with_wal);
|
||||
RecordTick(stats_, WRITE_WITH_WAL, write_with_wal);
|
||||
}
|
||||
return status;
|
||||
return io_s;
|
||||
}
|
||||
|
||||
Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used,
|
||||
SequenceNumber* last_sequence,
|
||||
size_t seq_inc) {
|
||||
Status status;
|
||||
IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
const WriteThread::WriteGroup& write_group, uint64_t* log_used,
|
||||
SequenceNumber* last_sequence, size_t seq_inc) {
|
||||
IOStatus io_s;
|
||||
|
||||
assert(!write_group.leader->disable_wal);
|
||||
// Same holds for all in the batch group
|
||||
@@ -1092,14 +1120,14 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
|
||||
log::Writer* log_writer = logs_.back().writer;
|
||||
uint64_t log_size;
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
io_s = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
|
||||
if (status.ok()) {
|
||||
if (io_s.ok()) {
|
||||
const bool concurrent = true;
|
||||
auto stats = default_cf_internal_stats_;
|
||||
stats->AddDBStats(InternalStats::kIntStatsWalFileBytes, log_size,
|
||||
@@ -1109,7 +1137,7 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
concurrent);
|
||||
RecordTick(stats_, WRITE_WITH_WAL, write_with_wal);
|
||||
}
|
||||
return status;
|
||||
return io_s;
|
||||
}
|
||||
|
||||
Status DBImpl::WriteRecoverableState() {
|
||||
|
||||
+104
-25
@@ -49,6 +49,8 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
read_callback_(read_callback),
|
||||
sequence_(s),
|
||||
statistics_(cf_options.statistics),
|
||||
max_skip_(max_sequential_skip_in_iterations),
|
||||
max_skippable_internal_keys_(read_options.max_skippable_internal_keys),
|
||||
num_internal_keys_skipped_(0),
|
||||
iterate_lower_bound_(read_options.iterate_lower_bound),
|
||||
iterate_upper_bound_(read_options.iterate_upper_bound),
|
||||
@@ -69,16 +71,17 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
range_del_agg_(&cf_options.internal_comparator, s),
|
||||
db_impl_(db_impl),
|
||||
cfd_(cfd),
|
||||
start_seqnum_(read_options.iter_start_seqnum) {
|
||||
start_seqnum_(read_options.iter_start_seqnum),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0) {
|
||||
RecordTick(statistics_, NO_ITERATOR_CREATED);
|
||||
max_skip_ = max_sequential_skip_in_iterations;
|
||||
max_skippable_internal_keys_ = read_options.max_skippable_internal_keys;
|
||||
if (pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
if (iter_.iter()) {
|
||||
iter_.iter()->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
assert(timestamp_size_ == user_comparator_.timestamp_size());
|
||||
}
|
||||
|
||||
Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
|
||||
@@ -220,9 +223,13 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
is_key_seqnum_zero_ = (ikey_.sequence == 0);
|
||||
|
||||
assert(iterate_upper_bound_ == nullptr || iter_.MayBeOutOfUpperBound() ||
|
||||
user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) < 0);
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
ikey_.user_key, /*a_has_ts=*/true, *iterate_upper_bound_,
|
||||
/*b_has_ts=*/false) < 0);
|
||||
if (iterate_upper_bound_ != nullptr && iter_.MayBeOutOfUpperBound() &&
|
||||
user_comparator_.Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) {
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
ikey_.user_key, /*a_has_ts=*/true, *iterate_upper_bound_,
|
||||
/*b_has_ts=*/false) >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -237,20 +244,25 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsVisible(ikey_.sequence)) {
|
||||
assert(ikey_.user_key.size() >= timestamp_size_);
|
||||
Slice ts;
|
||||
if (timestamp_size_ > 0) {
|
||||
ts = ExtractTimestampFromUserKey(ikey_.user_key, timestamp_size_);
|
||||
}
|
||||
if (IsVisible(ikey_.sequence, ts)) {
|
||||
// If the previous entry is of seqnum 0, the current entry will not
|
||||
// possibly be skipped. This condition can potentially be relaxed to
|
||||
// prev_key.seq <= ikey_.sequence. We are cautious because it will be more
|
||||
// prone to bugs causing the same user key with the same sequence number.
|
||||
if (!is_prev_key_seqnum_zero && skipping_saved_key &&
|
||||
user_comparator_.Compare(ikey_.user_key, saved_key_.GetUserKey()) <=
|
||||
0) {
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
ikey_.user_key, saved_key_.GetUserKey()) <= 0) {
|
||||
num_skipped++; // skip this entry
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
} else {
|
||||
assert(!skipping_saved_key ||
|
||||
user_comparator_.Compare(ikey_.user_key,
|
||||
saved_key_.GetUserKey()) > 0);
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
ikey_.user_key, saved_key_.GetUserKey()) > 0);
|
||||
num_skipped = 0;
|
||||
reseek_done = false;
|
||||
switch (ikey_.type) {
|
||||
@@ -356,8 +368,8 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
// This key was inserted after our snapshot was taken.
|
||||
// If this happens too many times in a row for the same user key, we want
|
||||
// to seek to the target sequence number.
|
||||
int cmp =
|
||||
user_comparator_.Compare(ikey_.user_key, saved_key_.GetUserKey());
|
||||
int cmp = user_comparator_.CompareWithoutTimestamp(
|
||||
ikey_.user_key, saved_key_.GetUserKey());
|
||||
if (cmp == 0 || (skipping_saved_key && cmp < 0)) {
|
||||
num_skipped++;
|
||||
} else {
|
||||
@@ -388,8 +400,17 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
// We're looking for the next user-key but all we see are the same
|
||||
// user-key with decreasing sequence numbers. Fast forward to
|
||||
// sequence number 0 and type deletion (the smallest type).
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetUserKey(),
|
||||
0, kTypeDeletion));
|
||||
if (timestamp_size_ == 0) {
|
||||
AppendInternalKey(
|
||||
&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), 0, kTypeDeletion));
|
||||
} else {
|
||||
std::string min_ts(timestamp_size_, static_cast<char>(0));
|
||||
AppendInternalKeyWithDifferentTimestamp(
|
||||
&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), 0, kTypeDeletion),
|
||||
min_ts);
|
||||
}
|
||||
// Don't set skipping_saved_key = false because we may still see more
|
||||
// user-keys equal to saved_key_.
|
||||
} else {
|
||||
@@ -398,9 +419,17 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
// Note that this only covers a case when a higher key was overwritten
|
||||
// many times since our snapshot was taken, not the case when a lot of
|
||||
// different keys were inserted after our snapshot was taken.
|
||||
AppendInternalKey(&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), sequence_,
|
||||
kValueTypeForSeek));
|
||||
if (timestamp_size_ == 0) {
|
||||
AppendInternalKey(
|
||||
&last_key, ParsedInternalKey(saved_key_.GetUserKey(), sequence_,
|
||||
kValueTypeForSeek));
|
||||
} else {
|
||||
AppendInternalKeyWithDifferentTimestamp(
|
||||
&last_key,
|
||||
ParsedInternalKey(saved_key_.GetUserKey(), sequence_,
|
||||
kValueTypeForSeek),
|
||||
*timestamp_ub_);
|
||||
}
|
||||
}
|
||||
iter_.Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
@@ -519,6 +548,13 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
}
|
||||
|
||||
void DBIter::Prev() {
|
||||
if (timestamp_size_ > 0) {
|
||||
valid_ = false;
|
||||
status_ = Status::NotSupported(
|
||||
"SeekToLast/SeekForPrev/Prev currently not supported with timestamp.");
|
||||
return;
|
||||
}
|
||||
|
||||
assert(valid_);
|
||||
assert(status_.ok());
|
||||
|
||||
@@ -697,7 +733,13 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsVisible(ikey.sequence) ||
|
||||
assert(ikey.user_key.size() >= timestamp_size_);
|
||||
Slice ts;
|
||||
if (timestamp_size_ > 0) {
|
||||
ts = Slice(ikey.user_key.data() + ikey.user_key.size() - timestamp_size_,
|
||||
timestamp_size_);
|
||||
}
|
||||
if (!IsVisible(ikey.sequence, ts) ||
|
||||
!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
break;
|
||||
}
|
||||
@@ -853,6 +895,13 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
if (!ParseKey(&ikey)) {
|
||||
return false;
|
||||
}
|
||||
assert(ikey.user_key.size() >= timestamp_size_);
|
||||
Slice ts;
|
||||
if (timestamp_size_ > 0) {
|
||||
ts = Slice(ikey.user_key.data() + ikey.user_key.size() - timestamp_size_,
|
||||
timestamp_size_);
|
||||
}
|
||||
|
||||
if (!user_comparator_.Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
// No visible values for this key, even though FindValueForCurrentKey()
|
||||
// has seen some. This is possible if we're using a tailing iterator, and
|
||||
@@ -861,7 +910,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsVisible(ikey.sequence)) {
|
||||
if (IsVisible(ikey.sequence, ts)) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1001,7 +1050,13 @@ bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
}
|
||||
|
||||
assert(ikey.sequence != kMaxSequenceNumber);
|
||||
if (!IsVisible(ikey.sequence)) {
|
||||
assert(ikey.user_key.size() >= timestamp_size_);
|
||||
Slice ts;
|
||||
if (timestamp_size_ > 0) {
|
||||
ts = Slice(ikey.user_key.data() + ikey.user_key.size() - timestamp_size_,
|
||||
timestamp_size_);
|
||||
}
|
||||
if (!IsVisible(ikey.sequence, ts)) {
|
||||
PERF_COUNTER_ADD(internal_recent_skipped_count, 1);
|
||||
} else {
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
@@ -1046,10 +1101,18 @@ bool DBIter::TooManyInternalKeysSkipped(bool increment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBIter::IsVisible(SequenceNumber sequence) {
|
||||
bool DBIter::IsVisible(SequenceNumber sequence, const Slice& ts) {
|
||||
// Remember that comparator orders preceding timestamp as larger.
|
||||
int cmp_ts = timestamp_ub_ != nullptr
|
||||
? user_comparator_.CompareTimestamp(ts, *timestamp_ub_)
|
||||
: 0;
|
||||
if (cmp_ts > 0) {
|
||||
return false;
|
||||
}
|
||||
if (read_callback_ == nullptr) {
|
||||
return sequence <= sequence_;
|
||||
} else {
|
||||
// TODO(yanqin): support timestamp in read_callback_.
|
||||
return read_callback_->IsVisible(sequence);
|
||||
}
|
||||
}
|
||||
@@ -1058,14 +1121,16 @@ void DBIter::SetSavedKeyToSeekTarget(const Slice& target) {
|
||||
is_key_seqnum_zero_ = false;
|
||||
SequenceNumber seq = sequence_;
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(target, seq);
|
||||
saved_key_.SetInternalKey(target, seq, kValueTypeForSeek, timestamp_ub_);
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_.Compare(saved_key_.GetUserKey(), *iterate_lower_bound_) <
|
||||
0) {
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
saved_key_.GetUserKey(), /*a_has_ts=*/true, *iterate_lower_bound_,
|
||||
/*b_has_ts=*/false) < 0) {
|
||||
// Seek key is smaller than the lower bound.
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_lower_bound_, seq);
|
||||
saved_key_.SetInternalKey(*iterate_lower_bound_, seq, kValueTypeForSeek,
|
||||
timestamp_ub_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1155,6 +1220,13 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (timestamp_size_ > 0) {
|
||||
valid_ = false;
|
||||
status_ = Status::NotSupported(
|
||||
"SeekToLast/SeekForPrev/Prev currently not supported with timestamp.");
|
||||
return;
|
||||
}
|
||||
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
@@ -1248,6 +1320,13 @@ void DBIter::SeekToFirst() {
|
||||
}
|
||||
|
||||
void DBIter::SeekToLast() {
|
||||
if (timestamp_size_ > 0) {
|
||||
valid_ = false;
|
||||
status_ = Status::NotSupported(
|
||||
"SeekToLast/SeekForPrev/Prev currently not supported with timestamp.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (iterate_upper_bound_ != nullptr) {
|
||||
// Seek to last key strictly less than ReadOptions.iterate_upper_bound.
|
||||
SeekForPrev(*iterate_upper_bound_);
|
||||
|
||||
+14
-2
@@ -146,7 +146,8 @@ class DBIter final : public Iterator {
|
||||
if (start_seqnum_ > 0) {
|
||||
return saved_key_.GetInternalKey();
|
||||
} else {
|
||||
return saved_key_.GetUserKey();
|
||||
const Slice ukey_and_ts = saved_key_.GetUserKey();
|
||||
return Slice(ukey_and_ts.data(), ukey_and_ts.size() - timestamp_size_);
|
||||
}
|
||||
}
|
||||
Slice value() const override {
|
||||
@@ -169,6 +170,13 @@ class DBIter final : public Iterator {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
Slice timestamp() const override {
|
||||
assert(valid_);
|
||||
assert(timestamp_size_ > 0);
|
||||
const Slice ukey_and_ts = saved_key_.GetUserKey();
|
||||
assert(timestamp_size_ < ukey_and_ts.size());
|
||||
return ExtractTimestampFromUserKey(ukey_and_ts, timestamp_size_);
|
||||
}
|
||||
bool IsBlob() const {
|
||||
assert(valid_ && (allow_blob_ || !is_blob_));
|
||||
return is_blob_;
|
||||
@@ -178,6 +186,8 @@ class DBIter final : public Iterator {
|
||||
|
||||
void Next() final override;
|
||||
void Prev() final override;
|
||||
// 'target' does not contain timestamp, even if user timestamp feature is
|
||||
// enabled.
|
||||
void Seek(const Slice& target) final override;
|
||||
void SeekForPrev(const Slice& target) final override;
|
||||
void SeekToFirst() final override;
|
||||
@@ -221,7 +231,7 @@ class DBIter final : public Iterator {
|
||||
// entry can be found within the prefix.
|
||||
void PrevInternal(const Slice* prefix);
|
||||
bool TooManyInternalKeysSkipped(bool increment = true);
|
||||
bool IsVisible(SequenceNumber sequence);
|
||||
bool IsVisible(SequenceNumber sequence, const Slice& ts);
|
||||
|
||||
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
||||
// is called
|
||||
@@ -327,6 +337,8 @@ class DBIter final : public Iterator {
|
||||
// for diff snapshots we want the lower bound on the seqnum;
|
||||
// if this value > 0 iterator will return internal keys
|
||||
SequenceNumber start_seqnum_;
|
||||
const Slice* const timestamp_ub_;
|
||||
const size_t timestamp_size_;
|
||||
};
|
||||
|
||||
// Return a new iterator that converts internal keys (yielded by
|
||||
|
||||
@@ -1873,7 +1873,7 @@ TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const int kNumKeys = 500;
|
||||
// Small number of merge operands to make sure that DBIter::Prev() dont
|
||||
// Small number of merge operands to make sure that DBIter::Prev() don't
|
||||
// fall back to Seek()
|
||||
const int kNumMergeOperands = 3;
|
||||
// Use value size that will make sure that every block contain 1 key
|
||||
@@ -1908,7 +1908,7 @@ TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocksRandomized) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Separate values and merge operands in different file so that we
|
||||
// make sure that we dont merge them while flushing but actually
|
||||
// make sure that we don't merge them while flushing but actually
|
||||
// merge them in the read path
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
if (rnd.PercentTrue(kNoMergeOpPercentage)) {
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
// Copyright (c) 2020-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
#ifdef OS_LINUX
|
||||
#include "env/io_posix.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class EnvWithCustomLogicalBlockSizeCache : public EnvWrapper {
|
||||
public:
|
||||
EnvWithCustomLogicalBlockSizeCache(Env* env, LogicalBlockSizeCache* cache)
|
||||
: EnvWrapper(env), cache_(cache) {}
|
||||
|
||||
Status RegisterDbPaths(const std::vector<std::string>& paths) override {
|
||||
return cache_->RefAndCacheLogicalBlockSize(paths);
|
||||
}
|
||||
|
||||
Status UnregisterDbPaths(const std::vector<std::string>& paths) override {
|
||||
cache_->UnrefAndTryRemoveCachedLogicalBlockSize(paths);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
LogicalBlockSizeCache* cache_;
|
||||
};
|
||||
|
||||
class DBLogicalBlockSizeCacheTest : public testing::Test {
|
||||
public:
|
||||
DBLogicalBlockSizeCacheTest()
|
||||
: dbname_(test::PerThreadDBPath("logical_block_size_cache_test")),
|
||||
data_path_0_(dbname_ + "/data_path_0"),
|
||||
data_path_1_(dbname_ + "/data_path_1"),
|
||||
cf_path_0_(dbname_ + "/cf_path_0"),
|
||||
cf_path_1_(dbname_ + "/cf_path_1") {
|
||||
auto get_fd_block_size = [&](int fd) { return fd; };
|
||||
auto get_dir_block_size = [&](const std::string& /*dir*/, size_t* size) {
|
||||
*size = 1024;
|
||||
return Status::OK();
|
||||
};
|
||||
cache_.reset(
|
||||
new LogicalBlockSizeCache(get_fd_block_size, get_dir_block_size));
|
||||
env_.reset(
|
||||
new EnvWithCustomLogicalBlockSizeCache(Env::Default(), cache_.get()));
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string dbname_;
|
||||
std::string data_path_0_;
|
||||
std::string data_path_1_;
|
||||
std::string cf_path_0_;
|
||||
std::string cf_path_1_;
|
||||
std::unique_ptr<LogicalBlockSizeCache> cache_;
|
||||
std::unique_ptr<Env> env_;
|
||||
};
|
||||
|
||||
TEST_F(DBLogicalBlockSizeCacheTest, OpenClose) {
|
||||
// Tests that Open will cache the logical block size for data paths,
|
||||
// and Close will remove the cached sizes.
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_.get();
|
||||
options.db_paths = {{data_path_0_, 2048}, {data_path_1_, 2048}};
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
DB* db;
|
||||
if (!i) {
|
||||
printf("Open\n");
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db));
|
||||
} else {
|
||||
#ifdef ROCKSDB_LITE
|
||||
break;
|
||||
#else
|
||||
printf("OpenForReadOnly\n");
|
||||
ASSERT_OK(DB::OpenForReadOnly(options, dbname_, &db));
|
||||
#endif
|
||||
}
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(data_path_1_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_1_));
|
||||
ASSERT_OK(db->Close());
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
delete db;
|
||||
}
|
||||
ASSERT_OK(DestroyDB(dbname_, options, {}));
|
||||
}
|
||||
|
||||
TEST_F(DBLogicalBlockSizeCacheTest, OpenDelete) {
|
||||
// Tests that Open will cache the logical block size for data paths,
|
||||
// and delete the db pointer will remove the cached sizes.
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_.get();
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
DB* db;
|
||||
if (!i) {
|
||||
printf("Open\n");
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db));
|
||||
} else {
|
||||
#ifdef ROCKSDB_LITE
|
||||
break;
|
||||
#else
|
||||
printf("OpenForReadOnly\n");
|
||||
ASSERT_OK(DB::OpenForReadOnly(options, dbname_, &db));
|
||||
#endif
|
||||
}
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
delete db;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
}
|
||||
ASSERT_OK(DestroyDB(dbname_, options, {}));
|
||||
}
|
||||
|
||||
TEST_F(DBLogicalBlockSizeCacheTest, CreateColumnFamily) {
|
||||
// Tests that CreateColumnFamily will cache the cf_paths,
|
||||
// drop the column family handle won't drop the cache,
|
||||
// drop and then delete the column family handle will drop the cache.
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_.get();
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.cf_paths = {{cf_path_0_, 1024}, {cf_path_1_, 2048}};
|
||||
|
||||
DB* db;
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db));
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
|
||||
ColumnFamilyHandle* cf = nullptr;
|
||||
ASSERT_OK(db->CreateColumnFamily(cf_options, "cf", &cf));
|
||||
ASSERT_EQ(3, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_1_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_));
|
||||
|
||||
// Drop column family does not drop cache.
|
||||
ASSERT_OK(db->DropColumnFamily(cf));
|
||||
ASSERT_EQ(3, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_1_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_));
|
||||
|
||||
// Delete handle will drop cache.
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cf));
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
ASSERT_OK(DestroyDB(dbname_, options, {{"cf", cf_options}}));
|
||||
}
|
||||
|
||||
TEST_F(DBLogicalBlockSizeCacheTest, CreateColumnFamilies) {
|
||||
// Tests that CreateColumnFamilies will cache the cf_paths,
|
||||
// drop the column family handle won't drop the cache,
|
||||
// drop and then delete the column family handle will drop the cache.
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_.get();
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.cf_paths = {{cf_path_0_, 1024}};
|
||||
|
||||
DB* db;
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db));
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfs;
|
||||
ASSERT_OK(db->CreateColumnFamilies(cf_options, {"cf1", "cf2"}, &cfs));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
// Drop column family does not drop cache.
|
||||
for (ColumnFamilyHandle* cf : cfs) {
|
||||
ASSERT_OK(db->DropColumnFamily(cf));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_));
|
||||
}
|
||||
|
||||
// Delete one handle will not drop cache because another handle is still
|
||||
// referencing cf_path_0_.
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[0]));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
// Delete the last handle will drop cache.
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[1]));
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
ASSERT_OK(DestroyDB(dbname_, options,
|
||||
{{"cf1", cf_options}, {"cf2", cf_options}}));
|
||||
}
|
||||
|
||||
TEST_F(DBLogicalBlockSizeCacheTest, OpenWithColumnFamilies) {
|
||||
// Tests that Open two column families with the same cf_path will cache the
|
||||
// cf_path and have 2 references to the cached size,
|
||||
// drop the column family handle won't drop the cache,
|
||||
// drop and then delete the column family handle will drop the cache.
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_.get();
|
||||
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.cf_paths = {{cf_path_0_, 1024}};
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
DB* db;
|
||||
ColumnFamilyHandle* cf1 = nullptr;
|
||||
ColumnFamilyHandle* cf2 = nullptr;
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db));
|
||||
ASSERT_OK(db->CreateColumnFamily(cf_options, "cf1", &cf1));
|
||||
ASSERT_OK(db->CreateColumnFamily(cf_options, "cf2", &cf2));
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cf1));
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cf2));
|
||||
delete db;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfs;
|
||||
if (!i) {
|
||||
printf("Open\n");
|
||||
ASSERT_OK(DB::Open(options, dbname_,
|
||||
{{"cf1", cf_options},
|
||||
{"cf2", cf_options},
|
||||
{"default", ColumnFamilyOptions()}},
|
||||
&cfs, &db));
|
||||
} else {
|
||||
#ifdef ROCKSDB_LITE
|
||||
break;
|
||||
#else
|
||||
printf("OpenForReadOnly\n");
|
||||
ASSERT_OK(DB::OpenForReadOnly(options, dbname_,
|
||||
{{"cf1", cf_options},
|
||||
{"cf2", cf_options},
|
||||
{"default", ColumnFamilyOptions()}},
|
||||
&cfs, &db));
|
||||
#endif
|
||||
}
|
||||
|
||||
// Logical block sizes of dbname_ and cf_path_0_ are cached during Open.
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
// Drop handles won't drop the cache.
|
||||
ASSERT_OK(db->DropColumnFamily(cfs[0]));
|
||||
ASSERT_OK(db->DropColumnFamily(cfs[1]));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
// Delete 1st handle won't drop the cache for cf_path_0_.
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[0]));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
// Delete 2nd handle will drop the cache for cf_path_0_.
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[1]));
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
|
||||
// Delete the default handle won't affect the cache because db still refers
|
||||
// to the default CF.
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[2]));
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
}
|
||||
ASSERT_OK(DestroyDB(dbname_, options,
|
||||
{{"cf1", cf_options}, {"cf2", cf_options}}));
|
||||
}
|
||||
|
||||
TEST_F(DBLogicalBlockSizeCacheTest, DestroyColumnFamilyHandle) {
|
||||
// Tests that destroy column family without dropping won't drop the cache,
|
||||
// because compaction and flush might still need to get logical block size
|
||||
// when opening new files.
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_.get();
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.cf_paths = {{cf_path_0_, 1024}};
|
||||
|
||||
DB* db;
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db));
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ColumnFamilyHandle* cf = nullptr;
|
||||
ASSERT_OK(db->CreateColumnFamily(cf_options, "cf", &cf));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
// Delete handle won't drop cache.
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cf));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
|
||||
// Open with column families.
|
||||
std::vector<ColumnFamilyHandle*> cfs;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (!i) {
|
||||
printf("Open\n");
|
||||
ASSERT_OK(DB::Open(
|
||||
options, dbname_,
|
||||
{{"cf", cf_options}, {"default", ColumnFamilyOptions()}}, &cfs, &db));
|
||||
} else {
|
||||
#ifdef ROCKSDB_LITE
|
||||
break;
|
||||
#else
|
||||
printf("OpenForReadOnly\n");
|
||||
ASSERT_OK(DB::OpenForReadOnly(
|
||||
options, dbname_,
|
||||
{{"cf", cf_options}, {"default", ColumnFamilyOptions()}}, &cfs, &db));
|
||||
#endif
|
||||
}
|
||||
// cf_path_0_ and dbname_ are cached.
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
// Deleting handle won't drop cache.
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[0]));
|
||||
ASSERT_OK(db->DestroyColumnFamilyHandle(cfs[1]));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(dbname_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(dbname_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
}
|
||||
ASSERT_OK(DestroyDB(dbname_, options, {{"cf", cf_options}}));
|
||||
}
|
||||
|
||||
TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithDifferentPaths) {
|
||||
// Tests the cache behavior when there are multiple DBs sharing the same env
|
||||
// with different db_paths and cf_paths.
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_.get();
|
||||
|
||||
ASSERT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
|
||||
DB* db0;
|
||||
ASSERT_OK(DB::Open(options, data_path_0_, &db0));
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
|
||||
ColumnFamilyOptions cf_options0;
|
||||
cf_options0.cf_paths = {{cf_path_0_, 1024}};
|
||||
ColumnFamilyHandle* cf0;
|
||||
db0->CreateColumnFamily(cf_options0, "cf", &cf0);
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
DB* db1;
|
||||
ASSERT_OK(DB::Open(options, data_path_1_, &db1));
|
||||
ASSERT_EQ(3, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(data_path_1_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_1_));
|
||||
|
||||
ColumnFamilyOptions cf_options1;
|
||||
cf_options1.cf_paths = {{cf_path_1_, 1024}};
|
||||
ColumnFamilyHandle* cf1;
|
||||
db1->CreateColumnFamily(cf_options1, "cf", &cf1);
|
||||
ASSERT_EQ(4, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(data_path_1_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_1_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_1_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_));
|
||||
|
||||
db0->DestroyColumnFamilyHandle(cf0);
|
||||
delete db0;
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_1_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_1_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_1_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_1_));
|
||||
ASSERT_OK(DestroyDB(data_path_0_, options, {{"cf", cf_options0}}));
|
||||
|
||||
db1->DestroyColumnFamilyHandle(cf1);
|
||||
delete db1;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
ASSERT_OK(DestroyDB(data_path_1_, options, {{"cf", cf_options1}}));
|
||||
}
|
||||
|
||||
TEST_F(DBLogicalBlockSizeCacheTest, MultiDBWithSamePaths) {
|
||||
// Tests the cache behavior when there are multiple DBs sharing the same env
|
||||
// with the same db_paths and cf_paths.
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = env_.get();
|
||||
options.db_paths = {{data_path_0_, 1024}};
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.cf_paths = {{cf_path_0_, 1024}};
|
||||
|
||||
ASSERT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
|
||||
DB* db0;
|
||||
ASSERT_OK(DB::Open(options, dbname_ + "/db0", &db0));
|
||||
ASSERT_EQ(1, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_0_));
|
||||
|
||||
ColumnFamilyHandle* cf0;
|
||||
db0->CreateColumnFamily(cf_options, "cf", &cf0);
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
DB* db1;
|
||||
ASSERT_OK(DB::Open(options, dbname_ + "/db1", &db1));
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(2, cache_->GetRefCount(data_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
ColumnFamilyHandle* cf1;
|
||||
db1->CreateColumnFamily(cf_options, "cf", &cf1);
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(2, cache_->GetRefCount(data_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(2, cache_->GetRefCount(cf_path_0_));
|
||||
|
||||
db0->DestroyColumnFamilyHandle(cf0);
|
||||
delete db0;
|
||||
ASSERT_EQ(2, cache_->Size());
|
||||
ASSERT_TRUE(cache_->Contains(data_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(data_path_0_));
|
||||
ASSERT_TRUE(cache_->Contains(cf_path_0_));
|
||||
ASSERT_EQ(1, cache_->GetRefCount(cf_path_0_));
|
||||
ASSERT_OK(DestroyDB(dbname_ + "/db0", options, {{"cf", cf_options}}));
|
||||
|
||||
db1->DestroyColumnFamilyHandle(cf1);
|
||||
delete db1;
|
||||
ASSERT_EQ(0, cache_->Size());
|
||||
ASSERT_OK(DestroyDB(dbname_ + "/db1", options, {{"cf", cf_options}}));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // OS_LINUX
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -261,7 +261,7 @@ TEST_F(DBMemTableTest, ConcurrentMergeWrite) {
|
||||
ReadOptions roptions;
|
||||
SequenceNumber max_covering_tombstone_seq = 0;
|
||||
LookupKey lkey("key", kMaxSequenceNumber);
|
||||
res = mem->Get(lkey, &value, &status, &merge_context,
|
||||
res = mem->Get(lkey, &value, /*timestamp=*/nullptr, &status, &merge_context,
|
||||
&max_covering_tombstone_seq, roptions);
|
||||
ASSERT_TRUE(res);
|
||||
uint64_t ivalue = DecodeFixed64(Slice(value).data());
|
||||
|
||||
@@ -861,6 +861,62 @@ TEST_F(DBOptionsTest, FIFOTtlBackwardCompatible) {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBOptionsTest, ChangeCompression) {
|
||||
if (!Snappy_Supported() || !LZ4_Supported()) {
|
||||
return;
|
||||
}
|
||||
Options options;
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.create_if_missing = true;
|
||||
options.compression = CompressionType::kLZ4Compression;
|
||||
options.bottommost_compression = CompressionType::kNoCompression;
|
||||
options.bottommost_compression_opts.level = 2;
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
CompressionType compression_used = CompressionType::kLZ4Compression;
|
||||
CompressionOptions compression_opt_used;
|
||||
bool compacted = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* c = reinterpret_cast<Compaction*>(arg);
|
||||
compression_used = c->output_compression();
|
||||
compression_opt_used = c->output_compression_opts();
|
||||
compacted = true;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_TRUE(compacted);
|
||||
ASSERT_EQ(CompressionType::kNoCompression, compression_used);
|
||||
ASSERT_EQ(options.compression_opts.level, compression_opt_used.level);
|
||||
|
||||
compression_used = CompressionType::kLZ4Compression;
|
||||
compacted = false;
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"bottommost_compression", "kSnappyCompression"},
|
||||
{"bottommost_compression_opts", "0:6:0:0:0:true"}}));
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "foofoofoo"));
|
||||
ASSERT_OK(Put("bar", "foofoofoo"));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_TRUE(compacted);
|
||||
ASSERT_EQ(CompressionType::kSnappyCompression, compression_used);
|
||||
ASSERT_EQ(6, compression_opt_used.level);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -45,7 +45,8 @@ void VerifyTableProperties(DB* db, uint64_t expected_entries_size) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class DBTablePropertiesTest : public DBTestBase {
|
||||
class DBTablePropertiesTest : public DBTestBase,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
public:
|
||||
DBTablePropertiesTest() : DBTestBase("/db_table_properties_test") {}
|
||||
TablePropertiesCollection TestGetPropertiesOfTablesInRange(
|
||||
@@ -251,7 +252,20 @@ TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
class DeletionTriggeredCompactionTestListener : public EventListener {
|
||||
public:
|
||||
void OnCompactionBegin(DB* , const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.compaction_reason,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
}
|
||||
|
||||
void OnCompactionCompleted(DB* , const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.compaction_reason,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
int kNumKeys = 1000;
|
||||
int kWindowSize = 100;
|
||||
int kNumDelsTrigger = 90;
|
||||
@@ -260,6 +274,10 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
|
||||
if(GetParam() == "kCompactionStyleUniversal") {
|
||||
opts.compaction_style = kCompactionStyleUniversal;
|
||||
}
|
||||
Reopen(opts);
|
||||
|
||||
// add an L1 file to prevent tombstones from dropping due to obsolescence
|
||||
@@ -268,6 +286,11 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
Flush();
|
||||
MoveFilesToLevel(1);
|
||||
|
||||
DeletionTriggeredCompactionTestListener *listener =
|
||||
new DeletionTriggeredCompactionTestListener();
|
||||
opts.listeners.emplace_back(listener);
|
||||
Reopen(opts);
|
||||
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
if (i >= kNumKeys - kWindowSize &&
|
||||
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
|
||||
@@ -280,7 +303,6 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Change the window size and deletion trigger and ensure new values take
|
||||
// effect
|
||||
@@ -302,7 +324,6 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
// Change the window size to disable delete triggered compaction
|
||||
kWindowSize = 0;
|
||||
@@ -322,9 +343,16 @@ TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTablePropertiesTest,
|
||||
DBTablePropertiesTest,
|
||||
::testing::Values(
|
||||
"kCompactionStyleLevel",
|
||||
"kCompactionStyleUniversal"
|
||||
));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
+7
-3
@@ -24,7 +24,7 @@
|
||||
#endif
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
#include "db/blob_index.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -4297,7 +4297,7 @@ TEST_P(DBTestWithParam, PreShutdownManualCompaction) {
|
||||
ASSERT_EQ("1,1,1", FilesPerLevel(1));
|
||||
|
||||
// Compaction range overlaps files
|
||||
Compact(1, "p1", "p9");
|
||||
Compact(1, "p", "q");
|
||||
ASSERT_EQ("0,0,1", FilesPerLevel(1));
|
||||
|
||||
// Populate a different range
|
||||
@@ -5370,6 +5370,8 @@ class DelayedMergeOperator : public MergeOperator {
|
||||
const char* Name() const override { return "DelayedMergeOperator"; }
|
||||
};
|
||||
|
||||
// TODO: hangs in CircleCI's Windows env
|
||||
#ifndef OS_WIN
|
||||
TEST_F(DBTest, MergeTestTime) {
|
||||
std::string one, two, three;
|
||||
PutFixed64(&one, 1);
|
||||
@@ -5417,6 +5419,7 @@ TEST_F(DBTest, MergeTestTime) {
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
this->env_->time_elapse_only_sleep_ = false;
|
||||
}
|
||||
#endif // OS_WIN
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_P(DBTestWithParam, MergeCompactionTimeTest) {
|
||||
@@ -5500,7 +5503,7 @@ TEST_F(DBTest, EmptyCompactedDB) {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBTest, SuggestCompactRangeTest) {
|
||||
TEST_F(DBTest, DISABLED_SuggestCompactRangeTest) {
|
||||
class CompactionFilterFactoryGetContext : public CompactionFilterFactory {
|
||||
public:
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
@@ -5608,6 +5611,7 @@ TEST_F(DBTest, SuggestCompactRangeTest) {
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(DBTest, PromoteL0) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
+4
-4
@@ -1678,12 +1678,12 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) {
|
||||
ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(fh, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 2, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
} else {
|
||||
ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
ASSERT_EQ(ih + 4, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
}
|
||||
|
||||
// Bloom and index hit will happen when a Get() happens.
|
||||
@@ -1692,12 +1692,12 @@ TEST_P(PinL0IndexAndFilterBlocksTest, DisablePrefetchingNonL0IndexAndFilter) {
|
||||
ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(fh + 1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
ASSERT_EQ(ih + 4, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
} else {
|
||||
ASSERT_EQ(fm + 3, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(fh + 2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
ASSERT_EQ(im + 3, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(ih + 4, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
ASSERT_EQ(ih + 5, TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1528,6 +1528,24 @@ TEST_F(DBWALTest, TruncateLastLogAfterRecoverWithoutFlush) {
|
||||
constexpr size_t kKB = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
// Test fallocate support of running file system.
|
||||
// Skip this test if fallocate is not supported.
|
||||
std::string fname_test_fallocate = dbname_ + "/preallocate_testfile";
|
||||
int fd = -1;
|
||||
do {
|
||||
fd = open(fname_test_fallocate.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0644);
|
||||
} while (fd < 0 && errno == EINTR);
|
||||
ASSERT_GT(fd, 0);
|
||||
int alloc_status = fallocate(fd, 0, 0, 1);
|
||||
int err_number = errno;
|
||||
close(fd);
|
||||
ASSERT_OK(options.env->DeleteFile(fname_test_fallocate));
|
||||
if (err_number == ENOSYS || err_number == EOPNOTSUPP) {
|
||||
fprintf(stderr, "Skipped preallocated space check: %s\n", strerror(err_number));
|
||||
return;
|
||||
}
|
||||
ASSERT_EQ(0, alloc_status);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
size_t preallocated_size =
|
||||
dbfull()->TEST_GetWalPreallocateBlockSize(options.write_buffer_size);
|
||||
|
||||
@@ -0,0 +1,898 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/block_based/block_builder.h"
|
||||
#include "test_util/fault_injection_test_env.h"
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "test_util/sync_point.h"
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class DBBasicTestWithTimestampBase : public DBTestBase {
|
||||
public:
|
||||
explicit DBBasicTestWithTimestampBase(const std::string& dbname)
|
||||
: DBTestBase(dbname) {}
|
||||
|
||||
protected:
|
||||
static std::string Key1(uint64_t k) {
|
||||
uint32_t x = 1;
|
||||
const bool is_little_endian = (*reinterpret_cast<char*>(&x) != 0);
|
||||
std::string ret;
|
||||
if (is_little_endian) {
|
||||
ret.assign(reinterpret_cast<char*>(&k), sizeof(k));
|
||||
} else {
|
||||
ret.resize(sizeof(k));
|
||||
ret[0] = k & 0xff;
|
||||
ret[1] = (k >> 8) & 0xff;
|
||||
ret[2] = (k >> 16) & 0xff;
|
||||
ret[3] = (k >> 24) & 0xff;
|
||||
ret[4] = (k >> 32) & 0xff;
|
||||
ret[5] = (k >> 40) & 0xff;
|
||||
ret[6] = (k >> 48) & 0xff;
|
||||
ret[7] = (k >> 56) & 0xff;
|
||||
}
|
||||
std::reverse(ret.begin(), ret.end());
|
||||
return ret;
|
||||
}
|
||||
|
||||
class TestComparator : public Comparator {
|
||||
private:
|
||||
const Comparator* cmp_without_ts_;
|
||||
|
||||
public:
|
||||
explicit TestComparator(size_t ts_sz)
|
||||
: Comparator(ts_sz), cmp_without_ts_(nullptr) {
|
||||
cmp_without_ts_ = BytewiseComparator();
|
||||
}
|
||||
|
||||
const char* Name() const override { return "TestComparator"; }
|
||||
|
||||
void FindShortSuccessor(std::string*) const override {}
|
||||
|
||||
void FindShortestSeparator(std::string*, const Slice&) const override {}
|
||||
|
||||
int Compare(const Slice& a, const Slice& b) const override {
|
||||
int r = CompareWithoutTimestamp(a, b);
|
||||
if (r != 0 || 0 == timestamp_size()) {
|
||||
return r;
|
||||
}
|
||||
return -CompareTimestamp(
|
||||
Slice(a.data() + a.size() - timestamp_size(), timestamp_size()),
|
||||
Slice(b.data() + b.size() - timestamp_size(), timestamp_size()));
|
||||
}
|
||||
|
||||
using Comparator::CompareWithoutTimestamp;
|
||||
int CompareWithoutTimestamp(const Slice& a, bool a_has_ts, const Slice& b,
|
||||
bool b_has_ts) const override {
|
||||
if (a_has_ts) {
|
||||
assert(a.size() >= timestamp_size());
|
||||
}
|
||||
if (b_has_ts) {
|
||||
assert(b.size() >= timestamp_size());
|
||||
}
|
||||
Slice lhs = a_has_ts ? StripTimestampFromUserKey(a, timestamp_size()) : a;
|
||||
Slice rhs = b_has_ts ? StripTimestampFromUserKey(b, timestamp_size()) : b;
|
||||
return cmp_without_ts_->Compare(lhs, rhs);
|
||||
}
|
||||
|
||||
int CompareTimestamp(const Slice& ts1, const Slice& ts2) const override {
|
||||
if (!ts1.data() && !ts2.data()) {
|
||||
return 0;
|
||||
} else if (ts1.data() && !ts2.data()) {
|
||||
return 1;
|
||||
} else if (!ts1.data() && ts2.data()) {
|
||||
return -1;
|
||||
}
|
||||
assert(ts1.size() == ts2.size());
|
||||
uint64_t low1 = 0;
|
||||
uint64_t low2 = 0;
|
||||
uint64_t high1 = 0;
|
||||
uint64_t high2 = 0;
|
||||
const size_t kSize = ts1.size();
|
||||
std::unique_ptr<char[]> ts1_buf(new char[kSize]);
|
||||
memcpy(ts1_buf.get(), ts1.data(), ts1.size());
|
||||
std::unique_ptr<char[]> ts2_buf(new char[kSize]);
|
||||
memcpy(ts2_buf.get(), ts2.data(), ts2.size());
|
||||
Slice ts1_copy = Slice(ts1_buf.get(), kSize);
|
||||
Slice ts2_copy = Slice(ts2_buf.get(), kSize);
|
||||
auto* ptr1 = const_cast<Slice*>(&ts1_copy);
|
||||
auto* ptr2 = const_cast<Slice*>(&ts2_copy);
|
||||
if (!GetFixed64(ptr1, &low1) || !GetFixed64(ptr1, &high1) ||
|
||||
!GetFixed64(ptr2, &low2) || !GetFixed64(ptr2, &high2)) {
|
||||
assert(false);
|
||||
}
|
||||
if (high1 < high2) {
|
||||
return -1;
|
||||
} else if (high1 > high2) {
|
||||
return 1;
|
||||
}
|
||||
if (low1 < low2) {
|
||||
return -1;
|
||||
} else if (low1 > low2) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
std::string Timestamp(uint64_t low, uint64_t high) {
|
||||
std::string ts;
|
||||
PutFixed64(&ts, low);
|
||||
PutFixed64(&ts, high);
|
||||
return ts;
|
||||
}
|
||||
|
||||
void CheckIterUserEntry(const Iterator* it, const Slice& expected_key,
|
||||
const Slice& expected_value,
|
||||
const Slice& expected_ts) const {
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(expected_key, it->key());
|
||||
ASSERT_EQ(expected_value, it->value());
|
||||
ASSERT_EQ(expected_ts, it->timestamp());
|
||||
}
|
||||
|
||||
void CheckIterEntry(const Iterator* it, const Slice& expected_ukey,
|
||||
SequenceNumber expected_seq, ValueType expected_val_type,
|
||||
const Slice& expected_value, const Slice& expected_ts) {
|
||||
ASSERT_TRUE(it->Valid());
|
||||
ASSERT_OK(it->status());
|
||||
std::string ukey_and_ts;
|
||||
ukey_and_ts.assign(expected_ukey.data(), expected_ukey.size());
|
||||
ukey_and_ts.append(expected_ts.data(), expected_ts.size());
|
||||
ParsedInternalKey parsed_ikey(ukey_and_ts, expected_seq, expected_val_type);
|
||||
std::string ikey;
|
||||
AppendInternalKey(&ikey, parsed_ikey);
|
||||
ASSERT_EQ(Slice(ikey), it->key());
|
||||
if (expected_val_type == kTypeValue) {
|
||||
ASSERT_EQ(expected_value, it->value());
|
||||
}
|
||||
ASSERT_EQ(expected_ts, it->timestamp());
|
||||
}
|
||||
};
|
||||
|
||||
class DBBasicTestWithTimestamp : public DBBasicTestWithTimestampBase {
|
||||
public:
|
||||
DBBasicTestWithTimestamp()
|
||||
: DBBasicTestWithTimestampBase("db_basic_test_with_timestamp") {}
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, SimpleForwardIterate) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 1024;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
// TODO(yanqin) re-enable auto compaction
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<uint64_t> start_keys = {1, 0};
|
||||
const std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(3, 0)};
|
||||
const std::vector<std::string> read_timestamps = {Timestamp(2, 0),
|
||||
Timestamp(4, 0)};
|
||||
for (size_t i = 0; i < write_timestamps.size(); ++i) {
|
||||
WriteOptions write_opts;
|
||||
Slice write_ts = write_timestamps[i];
|
||||
write_opts.timestamp = &write_ts;
|
||||
for (uint64_t key = start_keys[i]; key <= kMaxKey; ++key) {
|
||||
Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < read_timestamps.size(); ++i) {
|
||||
ReadOptions read_opts;
|
||||
Slice read_ts = read_timestamps[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
int count = 0;
|
||||
uint64_t key = 0;
|
||||
for (it->Seek(Key1(0)), key = start_keys[i]; it->Valid();
|
||||
it->Next(), ++count, ++key) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), "value" + std::to_string(i),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
size_t expected_count = kMaxKey - start_keys[i] + 1;
|
||||
ASSERT_EQ(expected_count, count);
|
||||
|
||||
// SeekToFirst() with lower bound.
|
||||
// Then iter with lower and upper bounds.
|
||||
uint64_t l = 0;
|
||||
uint64_t r = kMaxKey + 1;
|
||||
while (l < r) {
|
||||
std::string lb_str = Key1(l);
|
||||
Slice lb = lb_str;
|
||||
std::string ub_str = Key1(r);
|
||||
Slice ub = ub_str;
|
||||
read_opts.iterate_lower_bound = &lb;
|
||||
read_opts.iterate_upper_bound = &ub;
|
||||
it.reset(db_->NewIterator(read_opts));
|
||||
for (it->SeekToFirst(), key = std::max(l, start_keys[i]), count = 0;
|
||||
it->Valid(); it->Next(), ++key, ++count) {
|
||||
CheckIterUserEntry(it.get(), Key1(key), "value" + std::to_string(i),
|
||||
write_timestamps[i]);
|
||||
}
|
||||
ASSERT_EQ(r - std::max(l, start_keys[i]), count);
|
||||
l += (kMaxKey / 100);
|
||||
r -= (kMaxKey / 100);
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, ForwardIterateStartSeqnum) {
|
||||
const int kNumKeysPerFile = 128;
|
||||
const uint64_t kMaxKey = 0xffffffffffffffff;
|
||||
const uint64_t kMinKey = kMaxKey - 1023;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
// TODO(yanqin) re-enable auto compaction
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
DestroyAndReopen(options);
|
||||
std::vector<SequenceNumber> start_seqs;
|
||||
|
||||
const int kNumTimestamps = 4;
|
||||
std::vector<std::string> write_ts_list;
|
||||
for (int t = 0; t != kNumTimestamps; ++t) {
|
||||
write_ts_list.push_back(Timestamp(2 * t, /*do not care*/ 17));
|
||||
}
|
||||
WriteOptions write_opts;
|
||||
for (size_t i = 0; i != write_ts_list.size(); ++i) {
|
||||
Slice write_ts = write_ts_list[i];
|
||||
write_opts.timestamp = &write_ts;
|
||||
uint64_t k = kMinKey;
|
||||
do {
|
||||
Status s = db_->Put(write_opts, Key1(k), "value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
if (k == kMaxKey) {
|
||||
break;
|
||||
}
|
||||
++k;
|
||||
} while (k != 0);
|
||||
start_seqs.push_back(db_->GetLatestSequenceNumber());
|
||||
}
|
||||
std::vector<std::string> read_ts_list;
|
||||
for (int t = 0; t != kNumTimestamps - 1; ++t) {
|
||||
read_ts_list.push_back(Timestamp(2 * t + 3, /*do not care*/ 17));
|
||||
}
|
||||
ReadOptions read_opts;
|
||||
for (size_t i = 0; i != read_ts_list.size(); ++i) {
|
||||
Slice read_ts = read_ts_list[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
read_opts.iter_start_seqnum = start_seqs[i];
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
SequenceNumber expected_seq = start_seqs[i] + 1;
|
||||
uint64_t key = kMinKey;
|
||||
for (iter->Seek(Key1(kMinKey)); iter->Valid(); iter->Next()) {
|
||||
CheckIterEntry(iter.get(), Key1(key), expected_seq, kTypeValue,
|
||||
"value" + std::to_string(i + 1), write_ts_list[i + 1]);
|
||||
++key;
|
||||
++expected_seq;
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, ReseekToTargetTimestamp) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kNumKeys = 16;
|
||||
options.max_sequential_skip_in_iterations = kNumKeys / 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
// TODO(yanqin) re-enable auto compaction
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
// Insert kNumKeys
|
||||
WriteOptions write_opts;
|
||||
Status s;
|
||||
for (size_t i = 0; i != kNumKeys; ++i) {
|
||||
std::string ts_str = Timestamp(static_cast<uint64_t>(i + 1), 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
s = db_->Put(write_opts, "foo", "value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
ReadOptions read_opts;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
read_opts.timestamp = &ts;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
iter->SeekToFirst();
|
||||
CheckIterUserEntry(iter.get(), "foo", "value0", ts_str);
|
||||
ASSERT_EQ(
|
||||
1, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, ReseekToNextUserKey) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kNumKeys = 16;
|
||||
options.max_sequential_skip_in_iterations = kNumKeys / 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
// TODO(yanqin) re-enable auto compaction
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
// Write kNumKeys + 1 keys
|
||||
WriteOptions write_opts;
|
||||
Status s;
|
||||
for (size_t i = 0; i != kNumKeys; ++i) {
|
||||
std::string ts_str = Timestamp(static_cast<uint64_t>(i + 1), 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
s = db_->Put(write_opts, "a", "value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
std::string ts_str = Timestamp(static_cast<uint64_t>(kNumKeys + 1), 0);
|
||||
WriteBatch batch(0, 0, kTimestampSize);
|
||||
batch.Put("a", "new_value");
|
||||
batch.Put("b", "new_value");
|
||||
s = batch.AssignTimestamp(ts_str);
|
||||
ASSERT_OK(s);
|
||||
s = db_->Write(write_opts, &batch);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
ReadOptions read_opts;
|
||||
std::string ts_str = Timestamp(static_cast<uint64_t>(kNumKeys + 1), 0);
|
||||
Slice ts = ts_str;
|
||||
read_opts.timestamp = &ts;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
iter->Seek("a");
|
||||
iter->Next();
|
||||
CheckIterUserEntry(iter.get(), "b", "new_value", ts_str);
|
||||
ASSERT_EQ(
|
||||
1, options.statistics->getTickerCount(NUMBER_OF_RESEEKS_IN_ITERATION));
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, MaxKeysSkipped) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
DestroyAndReopen(options);
|
||||
constexpr size_t max_skippable_internal_keys = 2;
|
||||
const size_t kNumKeys = max_skippable_internal_keys + 2;
|
||||
WriteOptions write_opts;
|
||||
Status s;
|
||||
{
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
ASSERT_OK(db_->Put(write_opts, "a", "value"));
|
||||
}
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
std::string ts_str = Timestamp(static_cast<uint64_t>(i + 1), 0);
|
||||
Slice ts = ts_str;
|
||||
write_opts.timestamp = &ts;
|
||||
s = db_->Put(write_opts, "b", "value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
ReadOptions read_opts;
|
||||
read_opts.max_skippable_internal_keys = max_skippable_internal_keys;
|
||||
std::string ts_str = Timestamp(1, 0);
|
||||
Slice ts = ts_str;
|
||||
read_opts.timestamp = &ts;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
iter->SeekToFirst();
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->status().IsIncomplete());
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
class DBBasicTestWithTimestampCompressionSettings
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<std::tuple<
|
||||
std::shared_ptr<const FilterPolicy>, CompressionType, uint32_t>> {
|
||||
public:
|
||||
DBBasicTestWithTimestampCompressionSettings()
|
||||
: DBBasicTestWithTimestampBase(
|
||||
"db_basic_test_with_timestamp_compression") {}
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGet) {
|
||||
const int kNumKeysPerFile = 1024;
|
||||
const size_t kNumTimestamps = 4;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
size_t ts_sz = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(ts_sz);
|
||||
options.comparator = &test_cmp;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy = std::get<0>(GetParam());
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
|
||||
const CompressionType comp_type = std::get<1>(GetParam());
|
||||
#if LZ4_VERSION_NUMBER < 10400 // r124+
|
||||
if (comp_type == kLZ4Compression || comp_type == kLZ4HCCompression) {
|
||||
return;
|
||||
}
|
||||
#endif // LZ4_VERSION_NUMBER >= 10400
|
||||
if (!ZSTD_Supported() && comp_type == kZSTD) {
|
||||
return;
|
||||
}
|
||||
if (!Zlib_Supported() && comp_type == kZlibCompression) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.compression = comp_type;
|
||||
options.compression_opts.max_dict_bytes = std::get<2>(GetParam());
|
||||
if (comp_type == kZSTD) {
|
||||
options.compression_opts.zstd_max_train_bytes = std::get<2>(GetParam());
|
||||
}
|
||||
options.target_file_size_base = 1 << 26; // 64MB
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
std::vector<std::string> write_ts_list;
|
||||
std::vector<std::string> read_ts_list;
|
||||
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
write_ts_list.push_back(Timestamp(i * 2, 0));
|
||||
read_ts_list.push_back(Timestamp(1 + i * 2, 0));
|
||||
const Slice write_ts = write_ts_list.back();
|
||||
WriteOptions wopts;
|
||||
wopts.timestamp = &write_ts;
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
for (size_t j = 0; j != (kNumKeysPerFile - 1) / kNumTimestamps; ++j) {
|
||||
ASSERT_OK(Put(cf, Key1(j),
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
wopts));
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& verify_db_func = [&]() {
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
ReadOptions ropts;
|
||||
const Slice read_ts = read_ts_list[i];
|
||||
ropts.timestamp = &read_ts;
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
ColumnFamilyHandle* cfh = handles_[cf];
|
||||
for (size_t j = 0; j != (kNumKeysPerFile - 1) / kNumTimestamps; ++j) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ropts, cfh, Key1(j), &value));
|
||||
ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
verify_db_func();
|
||||
Close();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// A class which remembers the name of each flushed file.
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() override {}
|
||||
|
||||
void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFlushedFiles() {
|
||||
std::vector<std::string> result;
|
||||
{
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
result = flushed_files_;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ClearFlushedFiles() {
|
||||
InstrumentedMutexLock lock(&mutex_);
|
||||
flushed_files_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> flushed_files_;
|
||||
InstrumentedMutex mutex_;
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampCompressionSettings, PutAndGetWithCompaction) {
|
||||
const int kNumKeysPerFile = 1024;
|
||||
const size_t kNumTimestamps = 2;
|
||||
const size_t kNumKeysPerTimestamp = (kNumKeysPerFile - 1) / kNumTimestamps;
|
||||
const size_t kSplitPosBase = kNumKeysPerTimestamp / 2;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
|
||||
size_t ts_sz = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(ts_sz);
|
||||
options.comparator = &test_cmp;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy = std::get<0>(GetParam());
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
|
||||
const CompressionType comp_type = std::get<1>(GetParam());
|
||||
#if LZ4_VERSION_NUMBER < 10400 // r124+
|
||||
if (comp_type == kLZ4Compression || comp_type == kLZ4HCCompression) {
|
||||
return;
|
||||
}
|
||||
#endif // LZ4_VERSION_NUMBER >= 10400
|
||||
if (!ZSTD_Supported() && comp_type == kZSTD) {
|
||||
return;
|
||||
}
|
||||
if (!Zlib_Supported() && comp_type == kZlibCompression) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.compression = comp_type;
|
||||
options.compression_opts.max_dict_bytes = std::get<2>(GetParam());
|
||||
if (comp_type == kZSTD) {
|
||||
options.compression_opts.zstd_max_train_bytes = std::get<2>(GetParam());
|
||||
}
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
std::vector<std::string> write_ts_list;
|
||||
std::vector<std::string> read_ts_list;
|
||||
|
||||
const auto& verify_records_func = [&](size_t i, size_t begin, size_t end,
|
||||
ColumnFamilyHandle* cfh) {
|
||||
std::string value;
|
||||
std::string timestamp;
|
||||
|
||||
ReadOptions ropts;
|
||||
const Slice read_ts = read_ts_list[i];
|
||||
ropts.timestamp = &read_ts;
|
||||
std::string expected_timestamp =
|
||||
std::string(write_ts_list[i].data(), write_ts_list[i].size());
|
||||
|
||||
for (size_t j = begin; j <= end; ++j) {
|
||||
ASSERT_OK(db_->Get(ropts, cfh, Key1(j), &value, ×tamp));
|
||||
ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i), value);
|
||||
ASSERT_EQ(expected_timestamp, timestamp);
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
write_ts_list.push_back(Timestamp(i * 2, 0));
|
||||
read_ts_list.push_back(Timestamp(1 + i * 2, 0));
|
||||
const Slice write_ts = write_ts_list.back();
|
||||
WriteOptions wopts;
|
||||
wopts.timestamp = &write_ts;
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
size_t memtable_get_start = 0;
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
ASSERT_OK(Put(cf, Key1(j),
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
wopts));
|
||||
if (j == kSplitPosBase + i || j == kNumKeysPerTimestamp - 1) {
|
||||
verify_records_func(i, memtable_get_start, j, handles_[cf]);
|
||||
memtable_get_start = j + 1;
|
||||
|
||||
// flush all keys with the same timestamp to two sst files, split at
|
||||
// incremental positions such that lowerlevel[1].smallest.userkey ==
|
||||
// higherlevel[0].largest.userkey
|
||||
ASSERT_OK(Flush(cf));
|
||||
|
||||
// compact files (2 at each level) to a lower level such that all keys
|
||||
// with the same timestamp is at one level, with newer versions at
|
||||
// higher levels.
|
||||
CompactionOptions compact_opt;
|
||||
compact_opt.compression = kNoCompression;
|
||||
db_->CompactFiles(compact_opt, handles_[cf],
|
||||
collector->GetFlushedFiles(),
|
||||
static_cast<int>(kNumTimestamps - i));
|
||||
collector->ClearFlushedFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const auto& verify_db_func = [&]() {
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
ReadOptions ropts;
|
||||
const Slice read_ts = read_ts_list[i];
|
||||
ropts.timestamp = &read_ts;
|
||||
std::string expected_timestamp(write_ts_list[i].data(),
|
||||
write_ts_list[i].size());
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
ColumnFamilyHandle* cfh = handles_[cf];
|
||||
verify_records_func(i, 0, kNumKeysPerTimestamp - 1, cfh);
|
||||
}
|
||||
}
|
||||
};
|
||||
verify_db_func();
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTestWithTimestamp, BatchWriteAndMultiGet) {
|
||||
const int kNumKeysPerFile = 8192;
|
||||
const size_t kNumTimestamps = 2;
|
||||
const size_t kNumKeysPerTimestamp = (kNumKeysPerFile - 1) / kNumTimestamps;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
|
||||
size_t ts_sz = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(ts_sz);
|
||||
options.comparator = &test_cmp;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(
|
||||
10 /*bits_per_key*/, false /*use_block_based_builder*/));
|
||||
bbto.whole_key_filtering = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
size_t num_cfs = handles_.size();
|
||||
ASSERT_EQ(2, num_cfs);
|
||||
std::vector<std::string> write_ts_list;
|
||||
std::vector<std::string> read_ts_list;
|
||||
|
||||
const auto& verify_records_func = [&](size_t i, ColumnFamilyHandle* cfh) {
|
||||
std::vector<Slice> keys;
|
||||
std::vector<std::string> key_vals;
|
||||
std::vector<std::string> values;
|
||||
std::vector<std::string> timestamps;
|
||||
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
key_vals.push_back(Key1(j));
|
||||
}
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
keys.push_back(key_vals[j]);
|
||||
}
|
||||
|
||||
ReadOptions ropts;
|
||||
const Slice read_ts = read_ts_list[i];
|
||||
ropts.timestamp = &read_ts;
|
||||
std::string expected_timestamp(write_ts_list[i].data(),
|
||||
write_ts_list[i].size());
|
||||
|
||||
std::vector<ColumnFamilyHandle*> cfhs(keys.size(), cfh);
|
||||
std::vector<Status> statuses =
|
||||
db_->MultiGet(ropts, cfhs, keys, &values, ×tamps);
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
ASSERT_OK(statuses[j]);
|
||||
ASSERT_EQ("value_" + std::to_string(j) + "_" + std::to_string(i),
|
||||
values[j]);
|
||||
ASSERT_EQ(expected_timestamp, timestamps[j]);
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
write_ts_list.push_back(Timestamp(i * 2, 0));
|
||||
read_ts_list.push_back(Timestamp(1 + i * 2, 0));
|
||||
const Slice& write_ts = write_ts_list.back();
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
WriteOptions wopts;
|
||||
WriteBatch batch(0, 0, ts_sz);
|
||||
for (size_t j = 0; j != kNumKeysPerTimestamp; ++j) {
|
||||
ASSERT_OK(
|
||||
batch.Put(handles_[cf], Key1(j),
|
||||
"value_" + std::to_string(j) + "_" + std::to_string(i)));
|
||||
}
|
||||
batch.AssignTimestamp(write_ts);
|
||||
ASSERT_OK(db_->Write(wopts, &batch));
|
||||
|
||||
verify_records_func(i, handles_[cf]);
|
||||
|
||||
ASSERT_OK(Flush(cf));
|
||||
}
|
||||
}
|
||||
|
||||
const auto& verify_db_func = [&]() {
|
||||
for (size_t i = 0; i != kNumTimestamps; ++i) {
|
||||
ReadOptions ropts;
|
||||
const Slice read_ts = read_ts_list[i];
|
||||
ropts.timestamp = &read_ts;
|
||||
for (int cf = 0; cf != static_cast<int>(num_cfs); ++cf) {
|
||||
ColumnFamilyHandle* cfh = handles_[cf];
|
||||
verify_records_func(i, cfh);
|
||||
}
|
||||
}
|
||||
};
|
||||
verify_db_func();
|
||||
Close();
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Timestamp, DBBasicTestWithTimestampCompressionSettings,
|
||||
::testing::Combine(
|
||||
::testing::Values(std::shared_ptr<const FilterPolicy>(nullptr),
|
||||
std::shared_ptr<const FilterPolicy>(
|
||||
NewBloomFilterPolicy(10, false))),
|
||||
::testing::Values(kNoCompression, kZlibCompression, kLZ4Compression,
|
||||
kLZ4HCCompression, kZSTD),
|
||||
::testing::Values(0, 1 << 14)));
|
||||
|
||||
class DBBasicTestWithTimestampPrefixSeek
|
||||
: public DBBasicTestWithTimestampBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<std::shared_ptr<const SliceTransform>,
|
||||
std::shared_ptr<const FilterPolicy>, bool>> {
|
||||
public:
|
||||
DBBasicTestWithTimestampPrefixSeek()
|
||||
: DBBasicTestWithTimestampBase(
|
||||
"/db_basic_test_with_timestamp_prefix_seek") {}
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestWithTimestampPrefixSeek, ForwardIterateWithPrefix) {
|
||||
const size_t kNumKeysPerFile = 128;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
// TODO(yanqin): re-enable auto compactions
|
||||
options.disable_auto_compactions = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
options.prefix_extractor = std::get<0>(GetParam());
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy = std::get<1>(GetParam());
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const uint64_t kMaxKey = 0xffffffffffffffff;
|
||||
const uint64_t kMinKey = 0xfffffffffffff000;
|
||||
const std::vector<std::string> write_ts_list = {Timestamp(3, 0xffffffff),
|
||||
Timestamp(6, 0xffffffff)};
|
||||
WriteOptions write_opts;
|
||||
{
|
||||
for (size_t i = 0; i != write_ts_list.size(); ++i) {
|
||||
Slice write_ts = write_ts_list[i];
|
||||
write_opts.timestamp = &write_ts;
|
||||
uint64_t key = kMinKey;
|
||||
do {
|
||||
Status s = db_->Put(write_opts, Key1(key), "value" + std::to_string(i));
|
||||
ASSERT_OK(s);
|
||||
if (key == kMaxKey) {
|
||||
break;
|
||||
}
|
||||
++key;
|
||||
} while (true);
|
||||
}
|
||||
}
|
||||
const std::vector<std::string> read_ts_list = {Timestamp(5, 0xffffffff),
|
||||
Timestamp(9, 0xffffffff)};
|
||||
{
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = false;
|
||||
read_opts.prefix_same_as_start = std::get<2>(GetParam());
|
||||
fprintf(stdout, "%s %s %d\n", options.prefix_extractor->Name(),
|
||||
bbto.filter_policy ? bbto.filter_policy->Name() : "null",
|
||||
static_cast<int>(read_opts.prefix_same_as_start));
|
||||
for (size_t i = 0; i != read_ts_list.size(); ++i) {
|
||||
Slice read_ts = read_ts_list[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
|
||||
// Seek to kMaxKey
|
||||
iter->Seek(Key1(kMaxKey));
|
||||
CheckIterUserEntry(iter.get(), Key1(kMaxKey), "value" + std::to_string(i),
|
||||
write_ts_list[i]);
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
const std::vector<uint64_t> targets = {kMinKey, kMinKey + 0x10,
|
||||
kMinKey + 0x100, kMaxKey};
|
||||
const SliceTransform* const pe = options.prefix_extractor.get();
|
||||
ASSERT_NE(nullptr, pe);
|
||||
const size_t kPrefixShift =
|
||||
8 * (Key1(0).size() - pe->Transform(Key1(0)).size());
|
||||
const uint64_t kPrefixMask =
|
||||
~((static_cast<uint64_t>(1) << kPrefixShift) - 1);
|
||||
const uint64_t kNumKeysWithinPrefix =
|
||||
(static_cast<uint64_t>(1) << kPrefixShift);
|
||||
for (size_t i = 0; i != read_ts_list.size(); ++i) {
|
||||
Slice read_ts = read_ts_list[i];
|
||||
read_opts.timestamp = &read_ts;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
for (size_t j = 0; j != targets.size(); ++j) {
|
||||
std::string start_key = Key1(targets[j]);
|
||||
uint64_t expected_ub =
|
||||
(targets[j] & kPrefixMask) - 1 + kNumKeysWithinPrefix;
|
||||
uint64_t expected_key = targets[j];
|
||||
size_t count = 0;
|
||||
it->Seek(Key1(targets[j]));
|
||||
while (it->Valid()) {
|
||||
std::string saved_prev_key;
|
||||
saved_prev_key.assign(it->key().data(), it->key().size());
|
||||
|
||||
// Out of prefix
|
||||
if (!read_opts.prefix_same_as_start &&
|
||||
pe->Transform(saved_prev_key) != pe->Transform(start_key)) {
|
||||
break;
|
||||
}
|
||||
CheckIterUserEntry(it.get(), Key1(expected_key),
|
||||
"value" + std::to_string(i), write_ts_list[i]);
|
||||
++count;
|
||||
++expected_key;
|
||||
it->Next();
|
||||
}
|
||||
ASSERT_EQ(expected_ub - targets[j] + 1, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
// TODO(yanqin): consider handling non-fixed-length prefix extractors, e.g.
|
||||
// NoopTransform.
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
Timestamp, DBBasicTestWithTimestampPrefixSeek,
|
||||
::testing::Combine(
|
||||
::testing::Values(
|
||||
std::shared_ptr<const SliceTransform>(NewFixedPrefixTransform(4)),
|
||||
std::shared_ptr<const SliceTransform>(NewFixedPrefixTransform(7)),
|
||||
std::shared_ptr<const SliceTransform>(NewFixedPrefixTransform(8))),
|
||||
::testing::Values(std::shared_ptr<const FilterPolicy>(nullptr),
|
||||
std::shared_ptr<const FilterPolicy>(
|
||||
NewBloomFilterPolicy(10 /*bits_per_key*/, false)),
|
||||
std::shared_ptr<const FilterPolicy>(
|
||||
NewBloomFilterPolicy(20 /*bits_per_key*/,
|
||||
false))),
|
||||
::testing::Bool()));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -75,6 +75,15 @@ void AppendInternalKey(std::string* result, const ParsedInternalKey& key) {
|
||||
PutFixed64(result, PackSequenceAndType(key.sequence, key.type));
|
||||
}
|
||||
|
||||
void AppendInternalKeyWithDifferentTimestamp(std::string* result,
|
||||
const ParsedInternalKey& key,
|
||||
const Slice& ts) {
|
||||
assert(key.user_key.size() >= ts.size());
|
||||
result->append(key.user_key.data(), key.user_key.size() - ts.size());
|
||||
result->append(ts.data(), ts.size());
|
||||
PutFixed64(result, PackSequenceAndType(key.sequence, key.type));
|
||||
}
|
||||
|
||||
void AppendInternalKeyFooter(std::string* result, SequenceNumber s,
|
||||
ValueType t) {
|
||||
PutFixed64(result, PackSequenceAndType(s, t));
|
||||
|
||||
+27
-7
@@ -104,6 +104,7 @@ struct ParsedInternalKey {
|
||||
ParsedInternalKey()
|
||||
: sequence(kMaxSequenceNumber) // Make code analyzer happy
|
||||
{} // Intentionally left uninitialized (for speed)
|
||||
// u contains timestamp if user timestamp feature is enabled.
|
||||
ParsedInternalKey(const Slice& u, const SequenceNumber& seq, ValueType t)
|
||||
: user_key(u), sequence(seq), type(t) {}
|
||||
std::string DebugString(bool hex = false) const;
|
||||
@@ -132,6 +133,12 @@ EntryType GetEntryType(ValueType value_type);
|
||||
// Append the serialization of "key" to *result.
|
||||
extern void AppendInternalKey(std::string* result,
|
||||
const ParsedInternalKey& key);
|
||||
|
||||
// Append the serialization of "key" to *result, replacing the original
|
||||
// timestamp with argument ts.
|
||||
extern void AppendInternalKeyWithDifferentTimestamp(
|
||||
std::string* result, const ParsedInternalKey& key, const Slice& ts);
|
||||
|
||||
// Serialized internal key consists of user key followed by footer.
|
||||
// This function appends the footer to *result, assuming that *result already
|
||||
// contains the user key at the end.
|
||||
@@ -162,6 +169,11 @@ inline Slice StripTimestampFromUserKey(const Slice& user_key, size_t ts_sz) {
|
||||
return Slice(user_key.data(), user_key.size() - ts_sz);
|
||||
}
|
||||
|
||||
inline Slice ExtractTimestampFromUserKey(const Slice& user_key, size_t ts_sz) {
|
||||
assert(user_key.size() >= ts_sz);
|
||||
return Slice(user_key.data() + user_key.size() - ts_sz, ts_sz);
|
||||
}
|
||||
|
||||
inline uint64_t ExtractInternalKeyFooter(const Slice& internal_key) {
|
||||
assert(internal_key.size() >= 8);
|
||||
const size_t n = internal_key.size();
|
||||
@@ -187,7 +199,8 @@ class InternalKeyComparator
|
||||
|
||||
public:
|
||||
explicit InternalKeyComparator(const Comparator* c)
|
||||
: user_comparator_(c),
|
||||
: Comparator(c->timestamp_size()),
|
||||
user_comparator_(c),
|
||||
name_("rocksdb.InternalKeyComparator:" +
|
||||
std::string(user_comparator_.Name())) {}
|
||||
virtual ~InternalKeyComparator() {}
|
||||
@@ -434,24 +447,31 @@ class IterKey {
|
||||
|
||||
void SetInternalKey(const Slice& key_prefix, const Slice& user_key,
|
||||
SequenceNumber s,
|
||||
ValueType value_type = kValueTypeForSeek) {
|
||||
ValueType value_type = kValueTypeForSeek,
|
||||
const Slice* ts = nullptr) {
|
||||
size_t psize = key_prefix.size();
|
||||
size_t usize = user_key.size();
|
||||
EnlargeBufferIfNeeded(psize + usize + sizeof(uint64_t));
|
||||
size_t ts_sz = (ts != nullptr ? ts->size() : 0);
|
||||
EnlargeBufferIfNeeded(psize + usize + sizeof(uint64_t) + ts_sz);
|
||||
if (psize > 0) {
|
||||
memcpy(buf_, key_prefix.data(), psize);
|
||||
}
|
||||
memcpy(buf_ + psize, user_key.data(), usize);
|
||||
EncodeFixed64(buf_ + usize + psize, PackSequenceAndType(s, value_type));
|
||||
if (ts) {
|
||||
memcpy(buf_ + psize + usize, ts->data(), ts_sz);
|
||||
}
|
||||
EncodeFixed64(buf_ + usize + psize + ts_sz,
|
||||
PackSequenceAndType(s, value_type));
|
||||
|
||||
key_ = buf_;
|
||||
key_size_ = psize + usize + sizeof(uint64_t);
|
||||
key_size_ = psize + usize + sizeof(uint64_t) + ts_sz;
|
||||
is_user_key_ = false;
|
||||
}
|
||||
|
||||
void SetInternalKey(const Slice& user_key, SequenceNumber s,
|
||||
ValueType value_type = kValueTypeForSeek) {
|
||||
SetInternalKey(Slice(), user_key, s, value_type);
|
||||
ValueType value_type = kValueTypeForSeek,
|
||||
const Slice* ts = nullptr) {
|
||||
SetInternalKey(Slice(), user_key, s, value_type, ts);
|
||||
}
|
||||
|
||||
void Reserve(size_t size) {
|
||||
|
||||
@@ -238,6 +238,46 @@ Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reas
|
||||
return bg_error_;
|
||||
}
|
||||
|
||||
Status ErrorHandler::SetBGError(const IOStatus& bg_io_err,
|
||||
BackgroundErrorReason reason) {
|
||||
db_mutex_->AssertHeld();
|
||||
if (bg_io_err.ok()) {
|
||||
return Status::OK();
|
||||
}
|
||||
if (recovery_in_prog_ && recovery_error_.ok()) {
|
||||
recovery_error_ = bg_io_err;
|
||||
}
|
||||
Status new_bg_io_err = bg_io_err;
|
||||
Status s;
|
||||
if (bg_io_err.GetDataLoss()) {
|
||||
// FIrst, data loss is treated as unrecoverable error. So it can directly
|
||||
// overwrite any existing bg_error_.
|
||||
bool auto_recovery = false;
|
||||
Status bg_err(new_bg_io_err, Status::Severity::kUnrecoverableError);
|
||||
bg_error_ = bg_err;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s,
|
||||
db_mutex_, &auto_recovery);
|
||||
return bg_error_;
|
||||
} else if (bg_io_err.GetRetryable()) {
|
||||
// Second, check if the error is a retryable IO error or not. if it is
|
||||
// retryable error and its severity is higher than bg_error_, overwrite
|
||||
// the bg_error_ with new error.
|
||||
// In current stage, treat retryable error as HardError. No automatic
|
||||
// recovery.
|
||||
bool auto_recovery = false;
|
||||
Status bg_err(new_bg_io_err, Status::Severity::kHardError);
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s,
|
||||
db_mutex_, &auto_recovery);
|
||||
if (bg_err.severity() > bg_error_.severity()) {
|
||||
bg_error_ = bg_err;
|
||||
}
|
||||
return bg_error_;
|
||||
} else {
|
||||
s = SetBGError(new_bg_io_err, reason);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status ErrorHandler::OverrideNoSpaceError(Status bg_error,
|
||||
bool* auto_recovery) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -34,6 +35,8 @@ class ErrorHandler {
|
||||
|
||||
Status SetBGError(const Status& bg_err, BackgroundErrorReason reason);
|
||||
|
||||
Status SetBGError(const IOStatus& bg_io_err, BackgroundErrorReason reason);
|
||||
|
||||
Status GetBGError() { return bg_error_; }
|
||||
|
||||
Status GetRecoveryError() { return recovery_error_; }
|
||||
|
||||
@@ -10,18 +10,20 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
#include "test_util/fault_injection_test_env.h"
|
||||
#include "test_util/fault_injection_test_fs.h"
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "test_util/sync_point.h"
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBErrorHandlingTest : public DBTestBase {
|
||||
class DBErrorHandlingFSTest : public DBTestBase {
|
||||
public:
|
||||
DBErrorHandlingTest() : DBTestBase("/db_error_handling_test") {}
|
||||
DBErrorHandlingFSTest() : DBTestBase("/db_error_handling_fs_test") {}
|
||||
|
||||
std::string GetManifestNameFromLiveFiles() {
|
||||
std::vector<std::string> live_files;
|
||||
@@ -39,21 +41,24 @@ class DBErrorHandlingTest : public DBTestBase {
|
||||
}
|
||||
};
|
||||
|
||||
class DBErrorHandlingEnv : public EnvWrapper {
|
||||
public:
|
||||
DBErrorHandlingEnv() : EnvWrapper(Env::Default()),
|
||||
trig_no_space(false), trig_io_error(false) {}
|
||||
class DBErrorHandlingFS : public FileSystemWrapper {
|
||||
public:
|
||||
DBErrorHandlingFS()
|
||||
: FileSystemWrapper(FileSystem::Default()),
|
||||
trig_no_space(false),
|
||||
trig_io_error(false) {}
|
||||
|
||||
void SetTrigNoSpace() {trig_no_space = true;}
|
||||
void SetTrigIoError() {trig_io_error = true;}
|
||||
private:
|
||||
bool trig_no_space;
|
||||
bool trig_io_error;
|
||||
void SetTrigNoSpace() { trig_no_space = true; }
|
||||
void SetTrigIoError() { trig_io_error = true; }
|
||||
|
||||
private:
|
||||
bool trig_no_space;
|
||||
bool trig_io_error;
|
||||
};
|
||||
|
||||
class ErrorHandlerListener : public EventListener {
|
||||
class ErrorHandlerFSListener : public EventListener {
|
||||
public:
|
||||
ErrorHandlerListener()
|
||||
ErrorHandlerFSListener()
|
||||
: mutex_(),
|
||||
cv_(&mutex_),
|
||||
no_auto_recovery_(false),
|
||||
@@ -61,7 +66,7 @@ class ErrorHandlerListener : public EventListener {
|
||||
file_creation_started_(false),
|
||||
override_bg_error_(false),
|
||||
file_count_(0),
|
||||
fault_env_(nullptr) {}
|
||||
fault_fs_(nullptr) {}
|
||||
|
||||
void OnTableFileCreationStarted(
|
||||
const TableFileCreationBriefInfo& /*ti*/) override {
|
||||
@@ -69,16 +74,15 @@ class ErrorHandlerListener : public EventListener {
|
||||
file_creation_started_ = true;
|
||||
if (file_count_ > 0) {
|
||||
if (--file_count_ == 0) {
|
||||
fault_env_->SetFilesystemActive(false, file_creation_error_);
|
||||
file_creation_error_ = Status::OK();
|
||||
fault_fs_->SetFilesystemActive(false, file_creation_error_);
|
||||
file_creation_error_ = IOStatus::OK();
|
||||
}
|
||||
}
|
||||
cv_.SignalAll();
|
||||
}
|
||||
|
||||
void OnErrorRecoveryBegin(BackgroundErrorReason /*reason*/,
|
||||
Status /*bg_error*/,
|
||||
bool* auto_recovery) override {
|
||||
Status /*bg_error*/, bool* auto_recovery) override {
|
||||
if (*auto_recovery && no_auto_recovery_) {
|
||||
*auto_recovery = false;
|
||||
}
|
||||
@@ -125,11 +129,11 @@ class ErrorHandlerListener : public EventListener {
|
||||
override_bg_error_ = true;
|
||||
}
|
||||
|
||||
void InjectFileCreationError(FaultInjectionTestEnv* env, int file_count,
|
||||
Status s) {
|
||||
fault_env_ = env;
|
||||
void InjectFileCreationError(FaultInjectionTestFS* fs, int file_count,
|
||||
IOStatus io_s) {
|
||||
fault_fs_ = fs;
|
||||
file_count_ = file_count;
|
||||
file_creation_error_ = s;
|
||||
file_creation_error_ = io_s;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -140,18 +144,20 @@ class ErrorHandlerListener : public EventListener {
|
||||
bool file_creation_started_;
|
||||
bool override_bg_error_;
|
||||
int file_count_;
|
||||
Status file_creation_error_;
|
||||
IOStatus file_creation_error_;
|
||||
Status bg_error_;
|
||||
FaultInjectionTestEnv* fault_env_;
|
||||
FaultInjectionTestFS* fault_fs_;
|
||||
};
|
||||
|
||||
TEST_F(DBErrorHandlingTest, FLushWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
|
||||
@@ -159,15 +165,14 @@ TEST_F(DBErrorHandlingTest, FLushWriteError) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put(Key(0), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::Start", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) {
|
||||
fault_fs->SetFilesystemActive(false, IOStatus::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
@@ -176,13 +181,78 @@ TEST_F(DBErrorHandlingTest, FLushWriteError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, ManifestWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FLushWritRetryableeError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
Put(Key(1), "val1");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeFinishBuildTable",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val1", Get(Key(1)));
|
||||
|
||||
Put(Key(2), "val2");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeSyncTable",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val2", Get(Key(2)));
|
||||
|
||||
Put(Key(3), "val3");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BuildTable:BeforeCloseTableFile",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val3", Get(Key(3)));
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
@@ -196,15 +266,15 @@ TEST_F(DBErrorHandlingTest, ManifestWriteError) {
|
||||
Flush();
|
||||
Put(Key(1), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
fault_fs->SetFilesystemActive(false, IOStatus::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
@@ -217,13 +287,60 @@ TEST_F(DBErrorHandlingTest, ManifestWriteError) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, DoubleManifestWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, ManifestWriteRetryableError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
std::string new_manifest;
|
||||
|
||||
listener->EnableAutoRecovery(false);
|
||||
DestroyAndReopen(options);
|
||||
old_manifest = GetManifestNameFromLiveFiles();
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
Put(Key(0), "val");
|
||||
Flush();
|
||||
Put(Key(1), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, DoubleManifestWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
@@ -237,18 +354,18 @@ TEST_F(DBErrorHandlingTest, DoubleManifestWriteError) {
|
||||
Flush();
|
||||
Put(Key(1), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
fault_fs->SetFilesystemActive(false, IOStatus::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
|
||||
// This Resume() will attempt to create a new manifest file and fail again
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
@@ -265,15 +382,17 @@ TEST_F(DBErrorHandlingTest, DoubleManifestWriteError) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, CompactionManifestWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.env = fault_env.get();
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
std::string new_manifest;
|
||||
@@ -304,8 +423,8 @@ TEST_F(DBErrorHandlingTest, CompactionManifestWriteError) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
if (fail_manifest.load()) {
|
||||
fault_env->SetFilesystemActive(false,
|
||||
Status::NoSpace("Out of space"));
|
||||
fault_fs->SetFilesystemActive(false,
|
||||
IOStatus::NoSpace("Out of space"));
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -318,7 +437,7 @@ TEST_F(DBErrorHandlingTest, CompactionManifestWriteError) {
|
||||
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:0");
|
||||
// Clear all errors so when the compaction is retried, it will succeed
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:1");
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:2");
|
||||
@@ -336,15 +455,91 @@ TEST_F(DBErrorHandlingTest, CompactionManifestWriteError) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, CompactionWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionManifestWriteRetryableError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
std::string old_manifest;
|
||||
std::string new_manifest;
|
||||
std::atomic<bool> fail_manifest(false);
|
||||
DestroyAndReopen(options);
|
||||
old_manifest = GetManifestNameFromLiveFiles();
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
Put(Key(0), "val");
|
||||
Put(Key(2), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError));
|
||||
listener->EnableAutoRecovery(false);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
// Wait for flush of 2nd L0 file before starting compaction
|
||||
{{"DBImpl::FlushMemTable:FlushMemTableFinished",
|
||||
"BackgroundCallCompaction:0"},
|
||||
// Wait for compaction to detect manifest write error
|
||||
{"BackgroundCallCompaction:1", "CompactionManifestWriteError:0"},
|
||||
// Make compaction thread wait for error to be cleared
|
||||
{"CompactionManifestWriteError:1",
|
||||
"DBImpl::BackgroundCallCompaction:FoundObsoleteFiles"}});
|
||||
// trigger manifest write failure in compaction thread
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0", [&](void*) { fail_manifest.store(true); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifest", [&](void*) {
|
||||
if (fail_manifest.load()) {
|
||||
fault_fs->SetFilesystemActive(false,error_msg); }
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put(Key(1), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:0");
|
||||
TEST_SYNC_POINT("CompactionManifestWriteError:1");
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
new_manifest = GetManifestNameFromLiveFiles();
|
||||
ASSERT_NE(new_manifest, old_manifest);
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("val", Get(Key(0)));
|
||||
ASSERT_EQ("val", Get(Key(1)));
|
||||
ASSERT_EQ("val", Get(Key(2)));
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.env = fault_env.get();
|
||||
Status s;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -354,15 +549,14 @@ TEST_F(DBErrorHandlingTest, CompactionWriteError) {
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
listener->OverrideBGError(
|
||||
Status(Status::NoSpace(), Status::Severity::kHardError)
|
||||
);
|
||||
Status(Status::NoSpace(), Status::Severity::kHardError));
|
||||
listener->EnableAutoRecovery(false);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::FlushMemTable:FlushMemTableFinished",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0", [&](void*) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
fault_fs->SetFilesystemActive(false, IOStatus::NoSpace("Out of space"));
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -373,19 +567,67 @@ TEST_F(DBErrorHandlingTest, CompactionWriteError) {
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, CorruptionError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
TEST_F(DBErrorHandlingFSTest, CompactionWriteRetryableError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
Put(Key(0), "va;");
|
||||
Put(Key(2), "va;");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
listener->OverrideBGError(Status(error_msg, Status::Severity::kHardError));
|
||||
listener->EnableAutoRecovery(false);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::FlushMemTable:FlushMemTableFinished",
|
||||
"BackgroundCallCompaction:0"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0",
|
||||
[&](void*) { fault_fs->SetFilesystemActive(false, error_msg); });
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put(Key(1), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, CorruptionError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.env = fault_env.get();
|
||||
Status s;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -399,7 +641,8 @@ TEST_F(DBErrorHandlingTest, CorruptionError) {
|
||||
"BackgroundCallCompaction:0"}});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0", [&](void*) {
|
||||
fault_env->SetFilesystemActive(false, Status::Corruption("Corruption"));
|
||||
fault_fs->SetFilesystemActive(false,
|
||||
IOStatus::Corruption("Corruption"));
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -411,19 +654,21 @@ TEST_F(DBErrorHandlingTest, CorruptionError) {
|
||||
ASSERT_EQ(s.severity(),
|
||||
ROCKSDB_NAMESPACE::Status::Severity::kUnrecoverableError);
|
||||
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_NE(s, Status::OK());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, AutoRecoverFlushError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, AutoRecoverFlushError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
|
||||
@@ -432,13 +677,13 @@ TEST_F(DBErrorHandlingTest, AutoRecoverFlushError) {
|
||||
|
||||
Put(Key(0), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
fault_fs->SetFilesystemActive(false, IOStatus::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), ROCKSDB_NAMESPACE::Status::Severity::kHardError);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
|
||||
s = Put(Key(1), "val");
|
||||
@@ -450,13 +695,15 @@ TEST_F(DBErrorHandlingTest, AutoRecoverFlushError) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, FailRecoverFlushError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, FailRecoverFlushError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
|
||||
@@ -465,7 +712,7 @@ TEST_F(DBErrorHandlingTest, FailRecoverFlushError) {
|
||||
|
||||
Put(Key(0), "val");
|
||||
SyncPoint::GetInstance()->SetCallBack("FlushJob::Start", [&](void*) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
fault_fs->SetFilesystemActive(false, IOStatus::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
@@ -476,14 +723,16 @@ TEST_F(DBErrorHandlingTest, FailRecoverFlushError) {
|
||||
DestroyDB(dbname_, options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, WALWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, WALWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 32768;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
Random rnd(301);
|
||||
@@ -494,7 +743,7 @@ TEST_F(DBErrorHandlingTest, WALWriteError) {
|
||||
{
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto i = 0; i<100; ++i) {
|
||||
for (auto i = 0; i < 100; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
@@ -507,16 +756,18 @@ TEST_F(DBErrorHandlingTest, WALWriteError) {
|
||||
WriteBatch batch;
|
||||
int write_error = 0;
|
||||
|
||||
for (auto i = 100; i<199; ++i) {
|
||||
for (auto i = 100; i < 199; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
|
||||
write_error++;
|
||||
if (write_error > 2) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
|
||||
write_error++;
|
||||
if (write_error > 2) {
|
||||
fault_fs->SetFilesystemActive(false,
|
||||
IOStatus::NoSpace("Out of space"));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
@@ -524,9 +775,9 @@ TEST_F(DBErrorHandlingTest, WALWriteError) {
|
||||
ASSERT_EQ(s, s.NoSpace());
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
for (auto i=0; i<199; ++i) {
|
||||
for (auto i = 0; i < 199; ++i) {
|
||||
if (i < 100) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
} else {
|
||||
@@ -534,7 +785,7 @@ TEST_F(DBErrorHandlingTest, WALWriteError) {
|
||||
}
|
||||
}
|
||||
Reopen(options);
|
||||
for (auto i=0; i<199; ++i) {
|
||||
for (auto i = 0; i < 199; ++i) {
|
||||
if (i < 100) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
} else {
|
||||
@@ -544,14 +795,111 @@ TEST_F(DBErrorHandlingTest, WALWriteError) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, MultiCFWALWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
std::shared_ptr<ErrorHandlerListener> listener(new ErrorHandlerListener());
|
||||
TEST_F(DBErrorHandlingFSTest, WALWriteRetryableError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 32768;
|
||||
options.listeners.emplace_back(listener);
|
||||
options.paranoid_checks = true;
|
||||
Status s;
|
||||
Random rnd(301);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
|
||||
error_msg.SetRetryable(true);
|
||||
|
||||
// For the first batch, write is successful, require sync
|
||||
{
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto i = 0; i < 100; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK());
|
||||
};
|
||||
|
||||
// For the second batch, the first 2 file Append are successful, then the
|
||||
// following Append fails due to file system retryable IOError.
|
||||
{
|
||||
WriteBatch batch;
|
||||
int write_error = 0;
|
||||
|
||||
for (auto i = 100; i < 200; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
|
||||
write_error++;
|
||||
if (write_error > 2) {
|
||||
fault_fs->SetFilesystemActive(false, error_msg);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
s = dbfull()->Write(wopts, &batch);
|
||||
ASSERT_EQ(true, s.IsIOError());
|
||||
}
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
// Data in corrupted WAL are not stored
|
||||
for (auto i = 0; i < 199; ++i) {
|
||||
if (i < 100) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
} else {
|
||||
ASSERT_EQ(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
|
||||
// Resume and write a new batch, should be in the WAL
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
{
|
||||
WriteBatch batch;
|
||||
|
||||
for (auto i = 200; i < 300; ++i) {
|
||||
batch.Put(Key(i), RandomString(&rnd, 1024));
|
||||
}
|
||||
|
||||
WriteOptions wopts;
|
||||
wopts.sync = true;
|
||||
ASSERT_EQ(dbfull()->Write(wopts, &batch), Status::OK());
|
||||
};
|
||||
|
||||
Reopen(options);
|
||||
for (auto i = 0; i < 300; ++i) {
|
||||
if (i < 100 || i >= 200) {
|
||||
ASSERT_NE(Get(Key(i)), "NOT_FOUND");
|
||||
} else {
|
||||
ASSERT_EQ(Get(Key(i)), "NOT_FOUND");
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingFSTest, MultiCFWALWriteError) {
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs(
|
||||
new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
std::shared_ptr<ErrorHandlerFSListener> listener(
|
||||
new ErrorHandlerFSListener());
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 32768;
|
||||
options.env = fault_env.get();
|
||||
options.listeners.emplace_back(listener);
|
||||
Status s;
|
||||
Random rnd(301);
|
||||
@@ -586,8 +934,8 @@ TEST_F(DBErrorHandlingTest, MultiCFWALWriteError) {
|
||||
"WritableFileWriter::Append:BeforePrepareWrite", [&](void*) {
|
||||
write_error++;
|
||||
if (write_error > 2) {
|
||||
fault_env->SetFilesystemActive(false,
|
||||
Status::NoSpace("Out of space"));
|
||||
fault_fs->SetFilesystemActive(false,
|
||||
IOStatus::NoSpace("Out of space"));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -597,7 +945,7 @@ TEST_F(DBErrorHandlingTest, MultiCFWALWriteError) {
|
||||
ASSERT_EQ(s, s.NoSpace());
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
fault_env->SetFilesystemActive(true);
|
||||
fault_fs->SetFilesystemActive(true);
|
||||
ASSERT_EQ(listener->WaitForRecovery(5000000), true);
|
||||
|
||||
for (auto i = 1; i < 4; ++i) {
|
||||
@@ -627,24 +975,27 @@ TEST_F(DBErrorHandlingTest, MultiCFWALWriteError) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, MultiDBCompactionError) {
|
||||
TEST_F(DBErrorHandlingFSTest, MultiDBCompactionError) {
|
||||
FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default());
|
||||
std::vector<std::unique_ptr<FaultInjectionTestEnv>> fault_env;
|
||||
std::vector<std::unique_ptr<Env>> fault_envs;
|
||||
std::vector<FaultInjectionTestFS*> fault_fs;
|
||||
std::vector<Options> options;
|
||||
std::vector<std::shared_ptr<ErrorHandlerListener>> listener;
|
||||
std::vector<std::shared_ptr<ErrorHandlerFSListener>> listener;
|
||||
std::vector<DB*> db;
|
||||
std::shared_ptr<SstFileManager> sfm(NewSstFileManager(def_env));
|
||||
int kNumDbInstances = 3;
|
||||
Random rnd(301);
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
listener.emplace_back(new ErrorHandlerListener());
|
||||
listener.emplace_back(new ErrorHandlerFSListener());
|
||||
options.emplace_back(GetDefaultOptions());
|
||||
fault_env.emplace_back(new FaultInjectionTestEnv(Env::Default()));
|
||||
fault_fs.emplace_back(new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::shared_ptr<FileSystem> fs(fault_fs.back());
|
||||
fault_envs.emplace_back(new CompositeEnvWrapper(def_env, fs));
|
||||
options[i].env = fault_envs.back().get();
|
||||
options[i].create_if_missing = true;
|
||||
options[i].level0_file_num_compaction_trigger = 2;
|
||||
options[i].writable_file_max_buffer_size = 32768;
|
||||
options[i].env = fault_env[i].get();
|
||||
options[i].listeners.emplace_back(listener[i]);
|
||||
options[i].sst_file_manager = sfm;
|
||||
DB* dbptr;
|
||||
@@ -652,8 +1003,8 @@ TEST_F(DBErrorHandlingTest, MultiDBCompactionError) {
|
||||
|
||||
listener[i]->EnableAutoRecovery();
|
||||
// Setup for returning error for the 3rd SST, which would be level 1
|
||||
listener[i]->InjectFileCreationError(fault_env[i].get(), 3,
|
||||
Status::NoSpace("Out of space"));
|
||||
listener[i]->InjectFileCreationError(fault_fs[i], 3,
|
||||
IOStatus::NoSpace("Out of space"));
|
||||
snprintf(buf, sizeof(buf), "_%d", i);
|
||||
DestroyDB(dbname_ + std::string(buf), options[i]);
|
||||
ASSERT_EQ(DB::Open(options[i], dbname_ + std::string(buf), &dbptr),
|
||||
@@ -692,7 +1043,7 @@ TEST_F(DBErrorHandlingTest, MultiDBCompactionError) {
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
Status s = static_cast<DBImpl*>(db[i])->TEST_WaitForCompact(true);
|
||||
ASSERT_EQ(s.severity(), Status::Severity::kSoftError);
|
||||
fault_env[i]->SetFilesystemActive(true);
|
||||
fault_fs[i]->SetFilesystemActive(true);
|
||||
}
|
||||
|
||||
def_env->SetFilesystemActive(true);
|
||||
@@ -713,7 +1064,7 @@ TEST_F(DBErrorHandlingTest, MultiDBCompactionError) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "_%d", i);
|
||||
delete db[i];
|
||||
fault_env[i]->SetFilesystemActive(true);
|
||||
fault_fs[i]->SetFilesystemActive(true);
|
||||
if (getenv("KEEP_DB")) {
|
||||
printf("DB is still at %s%s\n", dbname_.c_str(), buf);
|
||||
} else {
|
||||
@@ -725,24 +1076,27 @@ TEST_F(DBErrorHandlingTest, MultiDBCompactionError) {
|
||||
delete def_env;
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, MultiDBVariousErrors) {
|
||||
TEST_F(DBErrorHandlingFSTest, MultiDBVariousErrors) {
|
||||
FaultInjectionTestEnv* def_env = new FaultInjectionTestEnv(Env::Default());
|
||||
std::vector<std::unique_ptr<FaultInjectionTestEnv>> fault_env;
|
||||
std::vector<std::unique_ptr<Env>> fault_envs;
|
||||
std::vector<FaultInjectionTestFS*> fault_fs;
|
||||
std::vector<Options> options;
|
||||
std::vector<std::shared_ptr<ErrorHandlerListener>> listener;
|
||||
std::vector<std::shared_ptr<ErrorHandlerFSListener>> listener;
|
||||
std::vector<DB*> db;
|
||||
std::shared_ptr<SstFileManager> sfm(NewSstFileManager(def_env));
|
||||
int kNumDbInstances = 3;
|
||||
Random rnd(301);
|
||||
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
listener.emplace_back(new ErrorHandlerListener());
|
||||
listener.emplace_back(new ErrorHandlerFSListener());
|
||||
options.emplace_back(GetDefaultOptions());
|
||||
fault_env.emplace_back(new FaultInjectionTestEnv(Env::Default()));
|
||||
fault_fs.emplace_back(new FaultInjectionTestFS(FileSystem::Default()));
|
||||
std::shared_ptr<FileSystem> fs(fault_fs.back());
|
||||
fault_envs.emplace_back(new CompositeEnvWrapper(def_env, fs));
|
||||
options[i].env = fault_envs.back().get();
|
||||
options[i].create_if_missing = true;
|
||||
options[i].level0_file_num_compaction_trigger = 2;
|
||||
options[i].writable_file_max_buffer_size = 32768;
|
||||
options[i].env = fault_env[i].get();
|
||||
options[i].listeners.emplace_back(listener[i]);
|
||||
options[i].sst_file_manager = sfm;
|
||||
DB* dbptr;
|
||||
@@ -752,14 +1106,14 @@ TEST_F(DBErrorHandlingTest, MultiDBVariousErrors) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
// Setup for returning error for the 3rd SST, which would be level 1
|
||||
listener[i]->InjectFileCreationError(fault_env[i].get(), 3,
|
||||
Status::NoSpace("Out of space"));
|
||||
listener[i]->InjectFileCreationError(fault_fs[i], 3,
|
||||
IOStatus::NoSpace("Out of space"));
|
||||
break;
|
||||
case 1:
|
||||
// Setup for returning error after the 1st SST, which would result
|
||||
// in a hard error
|
||||
listener[i]->InjectFileCreationError(fault_env[i].get(), 2,
|
||||
Status::NoSpace("Out of space"));
|
||||
listener[i]->InjectFileCreationError(fault_fs[i], 2,
|
||||
IOStatus::NoSpace("Out of space"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -816,7 +1170,7 @@ TEST_F(DBErrorHandlingTest, MultiDBVariousErrors) {
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
break;
|
||||
}
|
||||
fault_env[i]->SetFilesystemActive(true);
|
||||
fault_fs[i]->SetFilesystemActive(true);
|
||||
}
|
||||
|
||||
def_env->SetFilesystemActive(true);
|
||||
@@ -840,7 +1194,7 @@ TEST_F(DBErrorHandlingTest, MultiDBVariousErrors) {
|
||||
for (auto i = 0; i < kNumDbInstances; ++i) {
|
||||
char buf[16];
|
||||
snprintf(buf, sizeof(buf), "_%d", i);
|
||||
fault_env[i]->SetFilesystemActive(true);
|
||||
fault_fs[i]->SetFilesystemActive(true);
|
||||
delete db[i];
|
||||
if (getenv("KEEP_DB")) {
|
||||
printf("DB is still at %s%s\n", dbname_.c_str(), buf);
|
||||
@@ -851,7 +1205,6 @@ TEST_F(DBErrorHandlingTest, MultiDBVariousErrors) {
|
||||
options.clear();
|
||||
delete def_env;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@@ -796,6 +796,45 @@ TEST_F(ExternalSSTFileBasicTest, VerifyChecksumReadahead) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileBasicTest, IngestRangeDeletionTombstoneWithGlobalSeqno) {
|
||||
for (int i = 5; i < 25; i++) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), Key(i),
|
||||
Key(i) + "_val"));
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
|
||||
// file.sst (delete 0 => 30)
|
||||
std::string file = sst_files_dir_ + "file.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange(Key(0), Key(30)));
|
||||
ExternalSstFileInfo file_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file_info));
|
||||
ASSERT_EQ(file_info.file_path, file);
|
||||
ASSERT_EQ(file_info.num_entries, 0);
|
||||
ASSERT_EQ(file_info.smallest_key, "");
|
||||
ASSERT_EQ(file_info.largest_key, "");
|
||||
ASSERT_EQ(file_info.num_range_del_entries, 1);
|
||||
ASSERT_EQ(file_info.smallest_range_del_key, Key(0));
|
||||
ASSERT_EQ(file_info.largest_range_del_key, Key(30));
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.move_files = true;
|
||||
ifo.snapshot_consistency = true;
|
||||
ifo.allow_global_seqno = true;
|
||||
ifo.write_global_seqno = true;
|
||||
ifo.verify_checksums_before_ingest = false;
|
||||
ASSERT_OK(db_->IngestExternalFile({file}, ifo));
|
||||
|
||||
for (int i = 5; i < 25; i++) {
|
||||
std::string res;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &res).IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
int kNumLevels = 7;
|
||||
Options options = CurrentOptions();
|
||||
|
||||
@@ -46,7 +46,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily &&
|
||||
f.cf_id != cfd_->GetID()) {
|
||||
return Status::InvalidArgument(
|
||||
"External file column family id dont match");
|
||||
"External file column family id don't match");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
} else if (num_files > 1) {
|
||||
// Verify that passed files dont have overlapping ranges
|
||||
// Verify that passed files don't have overlapping ranges
|
||||
autovector<const IngestedFileInfo*> sorted_files;
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
sorted_files.push_back(&files_to_ingest_[i]);
|
||||
@@ -148,7 +148,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncDir");
|
||||
if (status.ok()) {
|
||||
for (auto path_id : ingestion_path_ids) {
|
||||
status = directories_->GetDataDir(path_id)->Fsync();
|
||||
status = directories_->GetDataDir(path_id)->Fsync(IOOptions(), nullptr);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to sync directory %" ROCKSDB_PRIszt
|
||||
@@ -212,7 +212,7 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
|
||||
if (ingestion_options_.snapshot_consistency && !db_snapshots_->empty()) {
|
||||
// We need to assign a global sequence number to all the files even
|
||||
// if the dont overlap with any ranges since we have snapshots
|
||||
// if the don't overlap with any ranges since we have snapshots
|
||||
force_global_seqno = true;
|
||||
}
|
||||
// It is safe to use this instead of LastAllocatedSequence since we are
|
||||
@@ -588,7 +588,7 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
continue;
|
||||
}
|
||||
|
||||
// We dont overlap with any keys in this level, but we still need to check
|
||||
// We don't overlap with any keys in this level, but we still need to check
|
||||
// if our file can fit in it
|
||||
if (IngestedFileFitInLevel(file_to_ingest, lvl)) {
|
||||
target_level = lvl;
|
||||
@@ -646,7 +646,7 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
return Status::InvalidArgument("Global seqno is required, but disabled");
|
||||
} else if (file_to_ingest->global_seqno_offset == 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Trying to set global seqno for a file that dont have a global seqno "
|
||||
"Trying to set global seqno for a file that don't have a global seqno "
|
||||
"field");
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "file/filename.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -476,17 +478,17 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
}
|
||||
ASSERT_NE(db_->GetLatestSequenceNumber(), 0U);
|
||||
|
||||
// Key range of file5 (400 => 499) dont overlap with any keys in DB
|
||||
// Key range of file5 (400 => 499) don't overlap with any keys in DB
|
||||
ASSERT_OK(DeprecatedAddFile({file5}));
|
||||
|
||||
// This file has overlapping values with the existing data
|
||||
s = DeprecatedAddFile({file6});
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Key range of file7 (500 => 598) dont overlap with any keys in DB
|
||||
// Key range of file7 (500 => 598) don't overlap with any keys in DB
|
||||
ASSERT_OK(DeprecatedAddFile({file7}));
|
||||
|
||||
// Key range of file7 (600 => 700) dont overlap with any keys in DB
|
||||
// Key range of file7 (600 => 700) don't overlap with any keys in DB
|
||||
ASSERT_OK(DeprecatedAddFile({file8}));
|
||||
|
||||
// Make sure values are correct before and after flush/compaction
|
||||
@@ -1609,15 +1611,15 @@ TEST_F(ExternalSSTFileTest, AddExternalSstFileWithCustomCompartor) {
|
||||
generated_files[7]};
|
||||
ASSERT_NOK(DeprecatedAddFile(in_files));
|
||||
|
||||
// These 2 files dont overlap with each other
|
||||
// These 2 files don't overlap with each other
|
||||
in_files = {generated_files[0], generated_files[2]};
|
||||
ASSERT_OK(DeprecatedAddFile(in_files));
|
||||
|
||||
// These 2 files dont overlap with each other but overlap with keys in DB
|
||||
// These 2 files don't overlap with each other but overlap with keys in DB
|
||||
in_files = {generated_files[3], generated_files[7]};
|
||||
ASSERT_NOK(DeprecatedAddFile(in_files));
|
||||
|
||||
// Files dont overlap and dont overlap with DB key range
|
||||
// Files don't overlap and don't overlap with DB key range
|
||||
in_files = {generated_files[4], generated_files[6], generated_files[8]};
|
||||
ASSERT_OK(DeprecatedAddFile(in_files));
|
||||
|
||||
@@ -1797,7 +1799,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
|
||||
// This file dont overlap with anything in the DB, will go to L4
|
||||
// This file don't overlap with anything in the DB, will go to L4
|
||||
ASSERT_EQ("0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
// Insert 80 -> 130 using AddFile
|
||||
@@ -1822,7 +1824,7 @@ TEST_P(ExternalSSTFileTest, IngestFileWithGlobalSeqnoAssignedLevel) {
|
||||
options, file_data, -1, true, write_global_seqno,
|
||||
verify_checksums_before_ingest, false, false, &true_data));
|
||||
|
||||
// This file dont overlap with anything in the DB and fit in L4 as well
|
||||
// This file don't overlap with anything in the DB and fit in L4 as well
|
||||
ASSERT_EQ("2,0,0,0,2", FilesPerLevel());
|
||||
|
||||
// Insert 10 -> 40 using AddFile
|
||||
@@ -2059,16 +2061,16 @@ TEST_F(ExternalSSTFileTest, FileWithCFInfo) {
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
|
||||
// SST CF dont match
|
||||
// SST CF don't match
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[0], {cf1_sst}, ifo));
|
||||
// SST CF dont match
|
||||
// SST CF don't match
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[2], {cf1_sst}, ifo));
|
||||
// SST CF match
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[1], {cf1_sst}, ifo));
|
||||
|
||||
// SST CF dont match
|
||||
// SST CF don't match
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[1], {cf_default_sst}, ifo));
|
||||
// SST CF dont match
|
||||
// SST CF don't match
|
||||
ASSERT_NOK(db_->IngestExternalFile(handles_[2], {cf_default_sst}, ifo));
|
||||
// SST CF match
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[0], {cf_default_sst}, ifo));
|
||||
@@ -2799,6 +2801,47 @@ TEST_P(ExternalSSTFileTest, IngestFilesTriggerFlushingWithTwoWriteQueue) {
|
||||
GenerateAndAddExternalFile(options, data);
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileTest, DeltaEncodingWhileGlobalSeqnoPresents) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
constexpr size_t kValueSize = 8;
|
||||
Random rnd(301);
|
||||
std::string value(RandomString(&rnd, kValueSize));
|
||||
|
||||
// Write some key to make global seqno larger than zero
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put("ab" + Key(i), value));
|
||||
}
|
||||
// Get a Snapshot to make RocksDB assign global seqno to ingested sst files.
|
||||
auto snap = dbfull()->GetSnapshot();
|
||||
|
||||
std::string fname = sst_files_dir_ + "test_file";
|
||||
rocksdb::SstFileWriter writer(EnvOptions(), options);
|
||||
ASSERT_OK(writer.Open(fname));
|
||||
std::string key1 = "ab";
|
||||
std::string key2 = "ab";
|
||||
|
||||
// Make the prefix of key2 is same with key1 add zero seqno. The tail of every
|
||||
// key is composed as (seqno << 8 | value_type), and here `1` represents
|
||||
// ValueType::kTypeValue
|
||||
|
||||
PutFixed64(&key2, PackSequenceAndType(0, kTypeValue));
|
||||
key2 += "cdefghijkl";
|
||||
|
||||
ASSERT_OK(writer.Put(key1, value));
|
||||
ASSERT_OK(writer.Put(key2, value));
|
||||
|
||||
ExternalSstFileInfo info;
|
||||
ASSERT_OK(writer.Finish(&info));
|
||||
|
||||
ASSERT_OK(dbfull()->IngestExternalFile({info.file_path},
|
||||
IngestExternalFileOptions()));
|
||||
dbfull()->ReleaseSnapshot(snap);
|
||||
ASSERT_EQ(value, Get(key1));
|
||||
// You will get error here
|
||||
ASSERT_EQ(value, Get(key2));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExternalSSTFileTest, ExternalSSTFileTest,
|
||||
testing::Values(std::make_tuple(false, false),
|
||||
std::make_tuple(false, true),
|
||||
|
||||
+15
-7
@@ -92,8 +92,8 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
LogBuffer* log_buffer, Directory* db_directory,
|
||||
Directory* output_file_directory,
|
||||
LogBuffer* log_buffer, FSDirectory* db_directory,
|
||||
FSDirectory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
@@ -238,10 +238,14 @@ Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
|
||||
} else if (write_manifest_) {
|
||||
TEST_SYNC_POINT("FlushJob::InstallResults");
|
||||
// Replace immutable memtable with the generated Table
|
||||
IOStatus tmp_io_s;
|
||||
s = cfd_->imm()->TryInstallMemtableFlushResults(
|
||||
cfd_, mutable_cf_options_, mems_, prep_tracker, versions_, db_mutex_,
|
||||
meta_.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
|
||||
log_buffer_, &committed_flush_jobs_info_);
|
||||
log_buffer_, &committed_flush_jobs_info_, &tmp_io_s);
|
||||
if (!tmp_io_s.ok()) {
|
||||
io_status_ = tmp_io_s;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok() && file_meta != nullptr) {
|
||||
@@ -376,6 +380,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
? current_time
|
||||
: meta_.oldest_ancester_time;
|
||||
|
||||
IOStatus io_s;
|
||||
s = BuildTable(
|
||||
dbname_, db_options_.env, db_options_.fs.get(), *cfd_->ioptions(),
|
||||
mutable_cf_options_, file_options_, cfd_->table_cache(), iter.get(),
|
||||
@@ -384,11 +389,14 @@ Status FlushJob::WriteLevel0Table() {
|
||||
cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, snapshot_checker_,
|
||||
output_compression_, mutable_cf_options_.sample_for_compression,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.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 */,
|
||||
TableFileCreationReason::kFlush, &io_s, event_logger_,
|
||||
job_context_->job_id, Env::IO_HIGH, &table_properties_, 0 /* level */,
|
||||
creation_time, oldest_key_time, write_hint, current_time);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
}
|
||||
LogFlush(db_options_.info_log);
|
||||
}
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
@@ -401,7 +409,7 @@ Status FlushJob::WriteLevel0Table() {
|
||||
meta_.marked_for_compaction ? " (needs compaction)" : "");
|
||||
|
||||
if (s.ok() && output_file_directory_ != nullptr && sync_output_directory_) {
|
||||
s = output_file_directory_->Fsync();
|
||||
s = output_file_directory_->Fsync(IOOptions(), nullptr);
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table", &mems_);
|
||||
db_mutex_->Lock();
|
||||
|
||||
+10
-5
@@ -67,9 +67,10 @@ class FlushJob {
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
LogBuffer* log_buffer, Directory* db_directory,
|
||||
Directory* output_file_directory, CompressionType output_compression,
|
||||
Statistics* stats, EventLogger* event_logger, bool measure_io_stats,
|
||||
LogBuffer* log_buffer, FSDirectory* db_directory,
|
||||
FSDirectory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger, bool measure_io_stats,
|
||||
const bool sync_output_directory, const bool write_manifest,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
@@ -89,6 +90,9 @@ class FlushJob {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Return the IO status
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
private:
|
||||
void ReportStartedFlush();
|
||||
void ReportFlushInputSize(const autovector<MemTable*>& mems);
|
||||
@@ -117,8 +121,8 @@ class FlushJob {
|
||||
SnapshotChecker* snapshot_checker_;
|
||||
JobContext* job_context_;
|
||||
LogBuffer* log_buffer_;
|
||||
Directory* db_directory_;
|
||||
Directory* output_file_directory_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* output_file_directory_;
|
||||
CompressionType output_compression_;
|
||||
Statistics* stats_;
|
||||
EventLogger* event_logger_;
|
||||
@@ -153,6 +157,7 @@ class FlushJob {
|
||||
Version* base_;
|
||||
bool pick_memtable_called;
|
||||
Env::Priority thread_pri_;
|
||||
IOStatus io_status_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob_index.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/flush_job.h"
|
||||
@@ -108,7 +108,7 @@ class FlushJobTest : public testing::Test {
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file that points to the new manifest file.
|
||||
s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob_index.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
+7
-7
@@ -37,10 +37,10 @@ Writer::~Writer() {
|
||||
}
|
||||
}
|
||||
|
||||
Status Writer::WriteBuffer() { return dest_->Flush(); }
|
||||
IOStatus Writer::WriteBuffer() { return dest_->Flush(); }
|
||||
|
||||
Status Writer::Close() {
|
||||
Status s;
|
||||
IOStatus Writer::Close() {
|
||||
IOStatus s;
|
||||
if (dest_) {
|
||||
s = dest_->Close();
|
||||
dest_.reset();
|
||||
@@ -48,7 +48,7 @@ Status Writer::Close() {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status Writer::AddRecord(const Slice& slice) {
|
||||
IOStatus Writer::AddRecord(const Slice& slice) {
|
||||
const char* ptr = slice.data();
|
||||
size_t left = slice.size();
|
||||
|
||||
@@ -59,7 +59,7 @@ Status Writer::AddRecord(const Slice& slice) {
|
||||
// Fragment the record if necessary and emit it. Note that if slice
|
||||
// is empty, we still want to iterate once to emit a single
|
||||
// zero-length record
|
||||
Status s;
|
||||
IOStatus s;
|
||||
bool begin = true;
|
||||
do {
|
||||
const int64_t leftover = kBlockSize - block_offset_;
|
||||
@@ -114,7 +114,7 @@ Status Writer::AddRecord(const Slice& slice) {
|
||||
|
||||
bool Writer::TEST_BufferIsEmpty() { return dest_->TEST_BufferIsEmpty(); }
|
||||
|
||||
Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
IOStatus Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
assert(n <= 0xffff); // Must fit in two bytes
|
||||
|
||||
size_t header_size;
|
||||
@@ -150,7 +150,7 @@ Status Writer::EmitPhysicalRecord(RecordType t, const char* ptr, size_t n) {
|
||||
EncodeFixed32(buf, crc);
|
||||
|
||||
// Write the header and the payload
|
||||
Status s = dest_->Append(Slice(buf, header_size));
|
||||
IOStatus s = dest_->Append(Slice(buf, header_size));
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(Slice(ptr, n));
|
||||
}
|
||||
|
||||
+5
-4
@@ -13,6 +13,7 @@
|
||||
#include <memory>
|
||||
|
||||
#include "db/log_format.h"
|
||||
#include "rocksdb/io_status.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -79,16 +80,16 @@ class Writer {
|
||||
|
||||
~Writer();
|
||||
|
||||
Status AddRecord(const Slice& slice);
|
||||
IOStatus AddRecord(const Slice& slice);
|
||||
|
||||
WritableFileWriter* file() { return dest_.get(); }
|
||||
const WritableFileWriter* file() const { return dest_.get(); }
|
||||
|
||||
uint64_t get_log_number() const { return log_number_; }
|
||||
|
||||
Status WriteBuffer();
|
||||
IOStatus WriteBuffer();
|
||||
|
||||
Status Close();
|
||||
IOStatus Close();
|
||||
|
||||
bool TEST_BufferIsEmpty();
|
||||
|
||||
@@ -103,7 +104,7 @@ class Writer {
|
||||
// record type stored in the header.
|
||||
uint32_t type_crc_[kMaxRecordType + 1];
|
||||
|
||||
Status EmitPhysicalRecord(RecordType type, const char* ptr, size_t length);
|
||||
IOStatus EmitPhysicalRecord(RecordType type, const char* ptr, size_t length);
|
||||
|
||||
// If true, it does not flush after each write. Instead it relies on the upper
|
||||
// layer to manually does the flush by calling ::WriteBuffer()
|
||||
|
||||
+154
-18
@@ -4,8 +4,6 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Test for issue 178: a manual compaction causes deleted data to reappear.
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "port/port.h"
|
||||
@@ -40,8 +38,8 @@ class ManualCompactionTest : public testing::Test {
|
||||
public:
|
||||
ManualCompactionTest() {
|
||||
// Get rid of any state from an old run.
|
||||
dbname_ = ROCKSDB_NAMESPACE::test::PerThreadDBPath("rocksdb_cbug_test");
|
||||
DestroyDB(dbname_, ROCKSDB_NAMESPACE::Options());
|
||||
dbname_ = test::PerThreadDBPath("rocksdb_manual_compaction_test");
|
||||
DestroyDB(dbname_, Options());
|
||||
}
|
||||
|
||||
std::string dbname_;
|
||||
@@ -60,6 +58,33 @@ class DestroyAllCompactionFilter : public CompactionFilter {
|
||||
const char* Name() const override { return "DestroyAllCompactionFilter"; }
|
||||
};
|
||||
|
||||
class LogCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
const char* Name() const override { return "LogCompactionFilter"; }
|
||||
|
||||
bool Filter(int level, const Slice& key, const Slice& /*existing_value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
key_level_[key.ToString()] = level;
|
||||
return false;
|
||||
}
|
||||
|
||||
void Reset() { key_level_.clear(); }
|
||||
|
||||
size_t NumKeys() const { return key_level_.size(); }
|
||||
|
||||
int KeyLevel(const Slice& key) {
|
||||
auto it = key_level_.find(key.ToString());
|
||||
if (it == key_level_.end()) {
|
||||
return -1;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::map<std::string, int> key_level_;
|
||||
};
|
||||
|
||||
TEST_F(ManualCompactionTest, CompactTouchesAllKeys) {
|
||||
for (int iter = 0; iter < 2; ++iter) {
|
||||
DB* db;
|
||||
@@ -71,7 +96,7 @@ TEST_F(ManualCompactionTest, CompactTouchesAllKeys) {
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
}
|
||||
options.create_if_missing = true;
|
||||
options.compression = ROCKSDB_NAMESPACE::kNoCompression;
|
||||
options.compression = kNoCompression;
|
||||
options.compaction_filter = new DestroyAllCompactionFilter();
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db));
|
||||
|
||||
@@ -100,46 +125,45 @@ TEST_F(ManualCompactionTest, Test) {
|
||||
// Open database. Disable compression since it affects the creation
|
||||
// of layers and the code below is trying to test against a very
|
||||
// specific scenario.
|
||||
ROCKSDB_NAMESPACE::DB* db;
|
||||
ROCKSDB_NAMESPACE::Options db_options;
|
||||
DB* db;
|
||||
Options db_options;
|
||||
db_options.write_buffer_size = 1024;
|
||||
db_options.create_if_missing = true;
|
||||
db_options.compression = ROCKSDB_NAMESPACE::kNoCompression;
|
||||
ASSERT_OK(ROCKSDB_NAMESPACE::DB::Open(db_options, dbname_, &db));
|
||||
db_options.compression = kNoCompression;
|
||||
ASSERT_OK(DB::Open(db_options, dbname_, &db));
|
||||
|
||||
// create first key range
|
||||
ROCKSDB_NAMESPACE::WriteBatch batch;
|
||||
WriteBatch batch;
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
batch.Put(Key1(i), "value for range 1 key");
|
||||
}
|
||||
ASSERT_OK(db->Write(ROCKSDB_NAMESPACE::WriteOptions(), &batch));
|
||||
ASSERT_OK(db->Write(WriteOptions(), &batch));
|
||||
|
||||
// create second key range
|
||||
batch.Clear();
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
batch.Put(Key2(i), "value for range 2 key");
|
||||
}
|
||||
ASSERT_OK(db->Write(ROCKSDB_NAMESPACE::WriteOptions(), &batch));
|
||||
ASSERT_OK(db->Write(WriteOptions(), &batch));
|
||||
|
||||
// delete second key range
|
||||
batch.Clear();
|
||||
for (int i = 0; i < kNumKeys; i++) {
|
||||
batch.Delete(Key2(i));
|
||||
}
|
||||
ASSERT_OK(db->Write(ROCKSDB_NAMESPACE::WriteOptions(), &batch));
|
||||
ASSERT_OK(db->Write(WriteOptions(), &batch));
|
||||
|
||||
// compact database
|
||||
std::string start_key = Key1(0);
|
||||
std::string end_key = Key1(kNumKeys - 1);
|
||||
ROCKSDB_NAMESPACE::Slice least(start_key.data(), start_key.size());
|
||||
ROCKSDB_NAMESPACE::Slice greatest(end_key.data(), end_key.size());
|
||||
Slice least(start_key.data(), start_key.size());
|
||||
Slice greatest(end_key.data(), end_key.size());
|
||||
|
||||
// commenting out the line below causes the example to work correctly
|
||||
db->CompactRange(CompactRangeOptions(), &least, &greatest);
|
||||
|
||||
// count the keys
|
||||
ROCKSDB_NAMESPACE::Iterator* iter =
|
||||
db->NewIterator(ROCKSDB_NAMESPACE::ReadOptions());
|
||||
Iterator* iter = db->NewIterator(ReadOptions());
|
||||
int num_keys = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
num_keys++;
|
||||
@@ -149,7 +173,119 @@ TEST_F(ManualCompactionTest, Test) {
|
||||
|
||||
// close database
|
||||
delete db;
|
||||
DestroyDB(dbname_, ROCKSDB_NAMESPACE::Options());
|
||||
DestroyDB(dbname_, Options());
|
||||
}
|
||||
|
||||
TEST_F(ManualCompactionTest, SkipLevel) {
|
||||
DB* db;
|
||||
Options options;
|
||||
options.num_levels = 3;
|
||||
// Initially, flushed L0 files won't exceed 100.
|
||||
options.level0_file_num_compaction_trigger = 100;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.create_if_missing = true;
|
||||
options.compression = kNoCompression;
|
||||
LogCompactionFilter* filter = new LogCompactionFilter();
|
||||
options.compaction_filter = filter;
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db));
|
||||
|
||||
WriteOptions wo;
|
||||
FlushOptions fo;
|
||||
ASSERT_OK(db->Put(wo, "1", ""));
|
||||
ASSERT_OK(db->Flush(fo));
|
||||
ASSERT_OK(db->Put(wo, "2", ""));
|
||||
ASSERT_OK(db->Flush(fo));
|
||||
ASSERT_OK(db->Put(wo, "4", ""));
|
||||
ASSERT_OK(db->Put(wo, "8", ""));
|
||||
ASSERT_OK(db->Flush(fo));
|
||||
|
||||
{
|
||||
// L0: 1, 2, [4, 8]
|
||||
// no file has keys in range [5, 7]
|
||||
Slice start("5");
|
||||
Slice end("7");
|
||||
filter->Reset();
|
||||
db->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_EQ(0, filter->NumKeys());
|
||||
}
|
||||
|
||||
{
|
||||
// L0: 1, 2, [4, 8]
|
||||
// [3, 7] overlaps with 4 in L0
|
||||
Slice start("3");
|
||||
Slice end("7");
|
||||
filter->Reset();
|
||||
db->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_EQ(2, filter->NumKeys());
|
||||
ASSERT_EQ(0, filter->KeyLevel("4"));
|
||||
ASSERT_EQ(0, filter->KeyLevel("8"));
|
||||
}
|
||||
|
||||
{
|
||||
// L0: 1, 2
|
||||
// L1: [4, 8]
|
||||
// no file has keys in range (-inf, 0]
|
||||
Slice end("0");
|
||||
filter->Reset();
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, &end);
|
||||
ASSERT_EQ(0, filter->NumKeys());
|
||||
}
|
||||
|
||||
{
|
||||
// L0: 1, 2
|
||||
// L1: [4, 8]
|
||||
// no file has keys in range [9, inf)
|
||||
Slice start("9");
|
||||
filter->Reset();
|
||||
db->CompactRange(CompactRangeOptions(), &start, nullptr);
|
||||
ASSERT_EQ(0, filter->NumKeys());
|
||||
}
|
||||
|
||||
{
|
||||
// L0: 1, 2
|
||||
// L1: [4, 8]
|
||||
// [2, 2] overlaps with 2 in L0
|
||||
Slice start("2");
|
||||
Slice end("2");
|
||||
filter->Reset();
|
||||
db->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_EQ(1, filter->NumKeys());
|
||||
ASSERT_EQ(0, filter->KeyLevel("2"));
|
||||
}
|
||||
|
||||
{
|
||||
// L0: 1
|
||||
// L1: 2, [4, 8]
|
||||
// [2, 5] overlaps with 2 and [4, 8) in L1, skip L0
|
||||
Slice start("2");
|
||||
Slice end("5");
|
||||
filter->Reset();
|
||||
db->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_EQ(3, filter->NumKeys());
|
||||
ASSERT_EQ(1, filter->KeyLevel("2"));
|
||||
ASSERT_EQ(1, filter->KeyLevel("4"));
|
||||
ASSERT_EQ(1, filter->KeyLevel("8"));
|
||||
}
|
||||
|
||||
{
|
||||
// L0: 1
|
||||
// L1: [2, 4, 8]
|
||||
// [0, inf) overlaps all files
|
||||
Slice start("0");
|
||||
filter->Reset();
|
||||
db->CompactRange(CompactRangeOptions(), &start, nullptr);
|
||||
ASSERT_EQ(4, filter->NumKeys());
|
||||
// 1 is first compacted to L1 and then further compacted into [2, 4, 8],
|
||||
// so finally the logged level for 1 is L1.
|
||||
ASSERT_EQ(1, filter->KeyLevel("1"));
|
||||
ASSERT_EQ(1, filter->KeyLevel("2"));
|
||||
ASSERT_EQ(1, filter->KeyLevel("4"));
|
||||
ASSERT_EQ(1, filter->KeyLevel("8"));
|
||||
}
|
||||
|
||||
delete filter;
|
||||
delete db;
|
||||
DestroyDB(dbname_, options);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
+19
-8
@@ -600,6 +600,7 @@ struct Saver {
|
||||
bool* merge_in_progress;
|
||||
std::string* value;
|
||||
SequenceNumber seq;
|
||||
std::string* timestamp;
|
||||
const MergeOperator* merge_operator;
|
||||
// the merge operations encountered;
|
||||
MergeContext* merge_context;
|
||||
@@ -643,9 +644,11 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
uint32_t key_length;
|
||||
const char* key_ptr = GetVarint32Ptr(entry, entry + 5, &key_length);
|
||||
Slice user_key_slice = Slice(key_ptr, key_length - 8);
|
||||
if (s->mem->GetInternalKeyComparator()
|
||||
.user_comparator()
|
||||
->CompareWithoutTimestamp(user_key_slice, s->key->user_key()) == 0) {
|
||||
const Comparator* user_comparator =
|
||||
s->mem->GetInternalKeyComparator().user_comparator();
|
||||
size_t ts_sz = user_comparator->timestamp_size();
|
||||
if (user_comparator->CompareWithoutTimestamp(user_key_slice,
|
||||
s->key->user_key()) == 0) {
|
||||
// Correct user key
|
||||
const uint64_t tag = DecodeFixed64(key_ptr + key_length - 8);
|
||||
ValueType type;
|
||||
@@ -713,6 +716,11 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
if (s->is_blob_index != nullptr) {
|
||||
*(s->is_blob_index) = (type == kTypeBlobIndex);
|
||||
}
|
||||
|
||||
if (ts_sz > 0 && s->timestamp != nullptr) {
|
||||
Slice ts = ExtractTimestampFromUserKey(user_key_slice, ts_sz);
|
||||
s->timestamp->assign(ts.data(), ts.size());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case kTypeDeletion:
|
||||
@@ -767,7 +775,8 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
bool MemTable::Get(const LookupKey& key, std::string* value,
|
||||
std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts,
|
||||
@@ -816,7 +825,7 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
PERF_COUNTER_ADD(bloom_memtable_hit_count, 1);
|
||||
}
|
||||
GetFromTable(key, *max_covering_tombstone_seq, do_merge, callback,
|
||||
is_blob_index, value, s, merge_context, seq,
|
||||
is_blob_index, value, timestamp, s, merge_context, seq,
|
||||
&found_final_value, &merge_in_progress);
|
||||
}
|
||||
|
||||
@@ -831,7 +840,8 @@ bool MemTable::Get(const LookupKey& key, std::string* value, Status* s,
|
||||
void MemTable::GetFromTable(const LookupKey& key,
|
||||
SequenceNumber max_covering_tombstone_seq,
|
||||
bool do_merge, ReadCallback* callback,
|
||||
bool* is_blob_index, std::string* value, Status* s,
|
||||
bool* is_blob_index, std::string* value,
|
||||
std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* seq,
|
||||
bool* found_final_value, bool* merge_in_progress) {
|
||||
Saver saver;
|
||||
@@ -840,6 +850,7 @@ void MemTable::GetFromTable(const LookupKey& key,
|
||||
saver.merge_in_progress = merge_in_progress;
|
||||
saver.key = &key;
|
||||
saver.value = value;
|
||||
saver.timestamp = timestamp;
|
||||
saver.seq = kMaxSequenceNumber;
|
||||
saver.mem = this;
|
||||
saver.merge_context = merge_context;
|
||||
@@ -908,8 +919,8 @@ void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
range_del_iter->MaxCoveringTombstoneSeqnum(iter->lkey->user_key()));
|
||||
}
|
||||
GetFromTable(*(iter->lkey), iter->max_covering_tombstone_seq, true,
|
||||
callback, is_blob, iter->value->GetSelf(), iter->s,
|
||||
&(iter->merge_context), &seq, &found_final_value,
|
||||
callback, is_blob, iter->value->GetSelf(), iter->timestamp,
|
||||
iter->s, &(iter->merge_context), &seq, &found_final_value,
|
||||
&merge_in_progress);
|
||||
|
||||
if (!found_final_value && merge_in_progress) {
|
||||
|
||||
+18
-7
@@ -212,16 +212,27 @@ class MemTable {
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr, bool do_merge = true) {
|
||||
return Get(key, value, /*timestamp=*/nullptr, s, merge_context,
|
||||
max_covering_tombstone_seq, seq, read_opts, callback,
|
||||
is_blob_index, do_merge);
|
||||
}
|
||||
|
||||
bool Get(const LookupKey& key, std::string* value, std::string* timestamp,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr, bool do_merge = true);
|
||||
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context,
|
||||
bool Get(const LookupKey& key, std::string* value, std::string* timestamp,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
const ReadOptions& read_opts, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr, bool do_merge = true) {
|
||||
SequenceNumber seq;
|
||||
return Get(key, value, s, merge_context, max_covering_tombstone_seq, &seq,
|
||||
read_opts, callback, is_blob_index, do_merge);
|
||||
return Get(key, value, timestamp, s, merge_context,
|
||||
max_covering_tombstone_seq, &seq, read_opts, callback,
|
||||
is_blob_index, do_merge);
|
||||
}
|
||||
|
||||
void MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
@@ -532,9 +543,9 @@ class MemTable {
|
||||
void GetFromTable(const LookupKey& key,
|
||||
SequenceNumber max_covering_tombstone_seq, bool do_merge,
|
||||
ReadCallback* callback, bool* is_blob_index,
|
||||
std::string* value, Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* seq, bool* found_final_value,
|
||||
bool* merge_in_progress);
|
||||
std::string* value, std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* seq,
|
||||
bool* found_final_value, bool* merge_in_progress);
|
||||
};
|
||||
|
||||
extern const char* EncodeKey(std::string* scratch, const Slice& target);
|
||||
|
||||
+18
-14
@@ -104,11 +104,12 @@ int MemTableList::NumFlushed() const {
|
||||
// Return the most recent value found, if any.
|
||||
// Operands stores the list of merge operations to apply, so far.
|
||||
bool MemTableListVersion::Get(const LookupKey& key, std::string* value,
|
||||
Status* s, MergeContext* merge_context,
|
||||
std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts,
|
||||
ReadCallback* callback, bool* is_blob_index) {
|
||||
return GetFromList(&memlist_, key, value, s, merge_context,
|
||||
return GetFromList(&memlist_, key, value, timestamp, s, merge_context,
|
||||
max_covering_tombstone_seq, seq, read_opts, callback,
|
||||
is_blob_index);
|
||||
}
|
||||
@@ -128,9 +129,9 @@ bool MemTableListVersion::GetMergeOperands(
|
||||
const LookupKey& key, Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, const ReadOptions& read_opts) {
|
||||
for (MemTable* memtable : memlist_) {
|
||||
bool done = memtable->Get(key, nullptr, s, merge_context,
|
||||
max_covering_tombstone_seq, read_opts, nullptr,
|
||||
nullptr, false);
|
||||
bool done = memtable->Get(key, /*value*/ nullptr, /*timestamp*/ nullptr, s,
|
||||
merge_context, max_covering_tombstone_seq,
|
||||
read_opts, nullptr, nullptr, false);
|
||||
if (done) {
|
||||
return true;
|
||||
}
|
||||
@@ -139,17 +140,17 @@ bool MemTableListVersion::GetMergeOperands(
|
||||
}
|
||||
|
||||
bool MemTableListVersion::GetFromHistory(
|
||||
const LookupKey& key, std::string* value, Status* s,
|
||||
const LookupKey& key, std::string* value, std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context, SequenceNumber* max_covering_tombstone_seq,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts, bool* is_blob_index) {
|
||||
return GetFromList(&memlist_history_, key, value, s, merge_context,
|
||||
return GetFromList(&memlist_history_, key, value, timestamp, s, merge_context,
|
||||
max_covering_tombstone_seq, seq, read_opts,
|
||||
nullptr /*read_callback*/, is_blob_index);
|
||||
}
|
||||
|
||||
bool MemTableListVersion::GetFromList(
|
||||
std::list<MemTable*>* list, const LookupKey& key, std::string* value,
|
||||
Status* s, MergeContext* merge_context,
|
||||
std::string* timestamp, Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts, ReadCallback* callback, bool* is_blob_index) {
|
||||
*seq = kMaxSequenceNumber;
|
||||
@@ -157,9 +158,9 @@ bool MemTableListVersion::GetFromList(
|
||||
for (auto& memtable : *list) {
|
||||
SequenceNumber current_seq = kMaxSequenceNumber;
|
||||
|
||||
bool done =
|
||||
memtable->Get(key, value, s, merge_context, max_covering_tombstone_seq,
|
||||
¤t_seq, read_opts, callback, is_blob_index);
|
||||
bool done = memtable->Get(key, value, timestamp, s, merge_context,
|
||||
max_covering_tombstone_seq, ¤t_seq,
|
||||
read_opts, callback, is_blob_index);
|
||||
if (*seq == kMaxSequenceNumber) {
|
||||
// Store the most recent sequence number of any operation on this key.
|
||||
// Since we only care about the most recent change, we only need to
|
||||
@@ -387,9 +388,10 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<MemTable*>& mems, LogsWithPrepTracker* prep_tracker,
|
||||
VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer,
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info) {
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info,
|
||||
IOStatus* io_s) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
|
||||
mu->AssertHeld();
|
||||
@@ -470,8 +472,10 @@ Status MemTableList::TryInstallMemtableFlushResults(
|
||||
}
|
||||
|
||||
// this can release and reacquire the mutex.
|
||||
vset->SetIOStatusOK();
|
||||
s = vset->LogAndApply(cfd, mutable_cf_options, edit_list, mu,
|
||||
db_directory);
|
||||
*io_s = vset->io_status();
|
||||
|
||||
// we will be changing the version in the next code path,
|
||||
// so we better create a new one, since versions are immutable
|
||||
@@ -641,7 +645,7 @@ Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
|
||||
InstrumentedMutex* mu, const autovector<FileMetaData*>& file_metas,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS);
|
||||
|
||||
+20
-15
@@ -58,20 +58,21 @@ class MemTableListVersion {
|
||||
// If any operation was found for this key, its most recent sequence number
|
||||
// will be stored in *seq on success (regardless of whether true/false is
|
||||
// returned). Otherwise, *seq will be set to kMaxSequenceNumber.
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context,
|
||||
bool Get(const LookupKey& key, std::string* value, std::string* timestamp,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, SequenceNumber* seq,
|
||||
const ReadOptions& read_opts, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr);
|
||||
|
||||
bool Get(const LookupKey& key, std::string* value, Status* s,
|
||||
MergeContext* merge_context,
|
||||
bool Get(const LookupKey& key, std::string* value, std::string* timestamp,
|
||||
Status* s, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
const ReadOptions& read_opts, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr) {
|
||||
SequenceNumber seq;
|
||||
return Get(key, value, s, merge_context, max_covering_tombstone_seq, &seq,
|
||||
read_opts, callback, is_blob_index);
|
||||
return Get(key, value, timestamp, s, merge_context,
|
||||
max_covering_tombstone_seq, &seq, read_opts, callback,
|
||||
is_blob_index);
|
||||
}
|
||||
|
||||
void MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
@@ -88,18 +89,20 @@ class MemTableListVersion {
|
||||
// have already been flushed. Should only be used from in-memory only
|
||||
// queries (such as Transaction validation) as the history may contain
|
||||
// writes that are also present in the SST files.
|
||||
bool GetFromHistory(const LookupKey& key, std::string* value, Status* s,
|
||||
bool GetFromHistory(const LookupKey& key, std::string* value,
|
||||
std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts,
|
||||
bool* is_blob_index = nullptr);
|
||||
bool GetFromHistory(const LookupKey& key, std::string* value, Status* s,
|
||||
bool GetFromHistory(const LookupKey& key, std::string* value,
|
||||
std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
const ReadOptions& read_opts,
|
||||
bool* is_blob_index = nullptr) {
|
||||
SequenceNumber seq;
|
||||
return GetFromHistory(key, value, s, merge_context,
|
||||
return GetFromHistory(key, value, timestamp, s, merge_context,
|
||||
max_covering_tombstone_seq, &seq, read_opts,
|
||||
is_blob_index);
|
||||
}
|
||||
@@ -137,7 +140,7 @@ class MemTableListVersion {
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
VersionSet* vset, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData*>& file_meta,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// REQUIRE: m is an immutable memtable
|
||||
@@ -148,7 +151,8 @@ class MemTableListVersion {
|
||||
void TrimHistory(autovector<MemTable*>* to_delete, size_t usage);
|
||||
|
||||
bool GetFromList(std::list<MemTable*>* list, const LookupKey& key,
|
||||
std::string* value, Status* s, MergeContext* merge_context,
|
||||
std::string* value, std::string* timestamp, Status* s,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
SequenceNumber* seq, const ReadOptions& read_opts,
|
||||
ReadCallback* callback = nullptr,
|
||||
@@ -260,9 +264,10 @@ class MemTableList {
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<MemTable*>& m, LogsWithPrepTracker* prep_tracker,
|
||||
VersionSet* vset, InstrumentedMutex* mu, uint64_t file_number,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer,
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info);
|
||||
std::list<std::unique_ptr<FlushJobInfo>>* committed_flush_jobs_info,
|
||||
IOStatus* io_s);
|
||||
|
||||
// New memtables are inserted at the front of the list.
|
||||
// Takes ownership of the referenced held on *m by the caller of Add().
|
||||
@@ -375,7 +380,7 @@ class MemTableList {
|
||||
const autovector<const autovector<MemTable*>*>& mems_list,
|
||||
VersionSet* vset, InstrumentedMutex* mu,
|
||||
const autovector<FileMetaData*>& file_meta,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
// DB mutex held
|
||||
@@ -417,6 +422,6 @@ extern Status InstallMemtableAtomicFlushResults(
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<const autovector<MemTable*>*>& mems_list, VersionSet* vset,
|
||||
InstrumentedMutex* mu, const autovector<FileMetaData*>& file_meta,
|
||||
autovector<MemTable*>* to_delete, Directory* db_directory,
|
||||
autovector<MemTable*>* to_delete, FSDirectory* db_directory,
|
||||
LogBuffer* log_buffer);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+58
-51
@@ -92,7 +92,6 @@ class MemTableListTest : public testing::Test {
|
||||
CreateDB();
|
||||
// Create a mock VersionSet
|
||||
DBOptions db_options;
|
||||
db_options.file_system = FileSystem::Default();
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
EnvOptions env_options;
|
||||
std::shared_ptr<Cache> table_cache(NewLRUCache(50000, 16));
|
||||
@@ -115,13 +114,14 @@ class MemTableListTest : public testing::Test {
|
||||
auto cfd = column_family_set->GetDefault();
|
||||
EXPECT_TRUE(nullptr != cfd);
|
||||
uint64_t file_num = file_number.fetch_add(1);
|
||||
IOStatus io_s;
|
||||
// Create dummy mutex.
|
||||
InstrumentedMutex mutex;
|
||||
InstrumentedMutexLock l(&mutex);
|
||||
std::list<std::unique_ptr<FlushJobInfo>> flush_jobs_info;
|
||||
Status s = list->TryInstallMemtableFlushResults(
|
||||
cfd, mutable_cf_options, m, &dummy_prep_tracker, &versions, &mutex,
|
||||
file_num, to_delete, nullptr, &log_buffer, &flush_jobs_info);
|
||||
file_num, to_delete, nullptr, &log_buffer, &flush_jobs_info, &io_s);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,6 @@ class MemTableListTest : public testing::Test {
|
||||
CreateDB();
|
||||
// Create a mock VersionSet
|
||||
DBOptions db_options;
|
||||
db_options.file_system.reset(new LegacyFileSystemWrapper(db_options.env));
|
||||
|
||||
ImmutableDBOptions immutable_db_options(db_options);
|
||||
EnvOptions env_options;
|
||||
@@ -221,8 +220,9 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
LookupKey lkey("key1", seq);
|
||||
bool found = list.current()->Get(lkey, &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
bool found = list.current()->Get(
|
||||
lkey, &value, /*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Create a MemTable
|
||||
@@ -244,19 +244,22 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
|
||||
// Fetch the newly written keys
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
found = mem->Get(LookupKey("key1", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value1");
|
||||
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key1", 2), &value, &s, &merge_context,
|
||||
found = mem->Get(LookupKey("key1", 2), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
// MemTable found out that this key is *not* found (at this sequence#)
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
found = mem->Get(LookupKey("key2", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value2.2");
|
||||
@@ -283,28 +286,29 @@ TEST_F(MemTableListTest, GetTest) {
|
||||
|
||||
// Fetch keys via MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(
|
||||
LookupKey("key1", seq), &value, /*timestamp*/nullptr, &s,
|
||||
&merge_context, &max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->Get(LookupKey("key1", saved_seq), &value, &s,
|
||||
&merge_context, &max_covering_tombstone_seq,
|
||||
ReadOptions());
|
||||
found = list.current()->Get(
|
||||
LookupKey("key1", saved_seq), &value, /*timestamp*/nullptr,
|
||||
&s, &merge_context, &max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ("value1", value);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(
|
||||
LookupKey("key2", seq), &value, /*timestamp*/nullptr, &s,
|
||||
&merge_context, &max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value2.3");
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->Get(LookupKey("key2", 1), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(
|
||||
LookupKey("key2", 1), &value, /*timestamp*/nullptr, &s,
|
||||
&merge_context, &max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
ASSERT_EQ(2, list.NumNotFlushed());
|
||||
@@ -333,8 +337,9 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
autovector<MemTable*> to_delete;
|
||||
|
||||
LookupKey lkey("key1", seq);
|
||||
bool found = list.current()->Get(lkey, &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
bool found = list.current()->Get(
|
||||
lkey, &value, /*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Create a MemTable
|
||||
@@ -355,13 +360,15 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Fetch the newly written keys
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
found = mem->Get(LookupKey("key1", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
// MemTable found out that this key is *not* found (at this sequence#)
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = mem->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
found = mem->Get(LookupKey("key2", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ(value, "value2.2");
|
||||
@@ -372,15 +379,15 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Fetch keys via MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(LookupKey("key1", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(s.ok() && found);
|
||||
ASSERT_EQ("value2.2", value);
|
||||
|
||||
@@ -400,27 +407,27 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Verify keys are no longer in MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(LookupKey("key1", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Verify keys are present in history
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(
|
||||
LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
LookupKey("key1", seq), &value, /*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(
|
||||
LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
LookupKey("key2", seq), &value, /*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(found);
|
||||
ASSERT_EQ("value2.2", value);
|
||||
@@ -462,42 +469,42 @@ TEST_F(MemTableListTest, GetFromHistoryTest) {
|
||||
|
||||
// Verify keys are no longer in MemTableList
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(LookupKey("key1", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key3", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(LookupKey("key3", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Verify that the second memtable's keys are in the history
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(
|
||||
LookupKey("key1", seq), &value, &s, &merge_context,
|
||||
LookupKey("key1", seq), &value, /*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(found && s.IsNotFound());
|
||||
|
||||
merge_context.Clear();
|
||||
found = list.current()->GetFromHistory(
|
||||
LookupKey("key3", seq), &value, &s, &merge_context,
|
||||
LookupKey("key3", seq), &value, /*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_TRUE(found);
|
||||
ASSERT_EQ("value3", value);
|
||||
|
||||
// Verify that key2 from the first memtable is no longer in the history
|
||||
merge_context.Clear();
|
||||
found =
|
||||
list.current()->Get(LookupKey("key2", seq), &value, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
found = list.current()->Get(LookupKey("key2", seq), &value,
|
||||
/*timestamp*/nullptr, &s, &merge_context,
|
||||
&max_covering_tombstone_seq, ReadOptions());
|
||||
ASSERT_FALSE(found);
|
||||
|
||||
// Cleanup
|
||||
|
||||
@@ -1275,7 +1275,7 @@ static std::string RandomString(Random* rnd, int len) {
|
||||
|
||||
TEST_P(PlainTableDBTest, CompactionTrigger) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 120 << 10; // 100KB
|
||||
options.write_buffer_size = 120 << 10; // 120KB
|
||||
options.num_levels = 3;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
Reopen(&options);
|
||||
@@ -1287,7 +1287,7 @@ TEST_P(PlainTableDBTest, CompactionTrigger) {
|
||||
std::vector<std::string> values;
|
||||
// Write 120KB (10 values, each 12K)
|
||||
for (int i = 0; i < 10; i++) {
|
||||
values.push_back(RandomString(&rnd, 12000));
|
||||
values.push_back(RandomString(&rnd, 12 << 10));
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
ASSERT_OK(Put(Key(999), ""));
|
||||
|
||||
+2
-5
@@ -424,6 +424,7 @@ class Repairer {
|
||||
}
|
||||
|
||||
LegacyFileSystemWrapper fs(env_);
|
||||
IOStatus io_s;
|
||||
status = BuildTable(
|
||||
dbname_, env_, &fs, *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(), env_options_, table_cache_,
|
||||
@@ -432,7 +433,7 @@ class Repairer {
|
||||
cfd->GetID(), cfd->GetName(), {}, kMaxSequenceNumber,
|
||||
snapshot_checker, kNoCompression, 0 /* sample_for_compression */,
|
||||
CompressionOptions(), false, nullptr /* internal_stats */,
|
||||
TableFileCreationReason::kRecovery, nullptr /* event_logger */,
|
||||
TableFileCreationReason::kRecovery, &io_s, nullptr /* event_logger */,
|
||||
0 /* job_id */, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
-1 /* level */, current_time, write_hint);
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
@@ -672,10 +673,6 @@ Status RepairDB(const std::string& dbname, const DBOptions& db_options,
|
||||
|
||||
Status RepairDB(const std::string& dbname, const Options& options) {
|
||||
Options opts(options);
|
||||
if (opts.file_system == nullptr) {
|
||||
opts.file_system.reset(new LegacyFileSystemWrapper(opts.env));
|
||||
;
|
||||
}
|
||||
|
||||
DBOptions db_options(opts);
|
||||
ColumnFamilyOptions cf_options(opts);
|
||||
|
||||
+265
-15
@@ -14,13 +14,16 @@
|
||||
#include <cinttypes>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/table_cache.h"
|
||||
@@ -100,6 +103,9 @@ class VersionBuilder::Rep {
|
||||
FileComparator level_zero_cmp_;
|
||||
FileComparator level_nonzero_cmp_;
|
||||
|
||||
// Metadata for all blob files affected by the series of version edits.
|
||||
std::map<uint64_t, std::shared_ptr<BlobFileMetaData>> changed_blob_files_;
|
||||
|
||||
public:
|
||||
Rep(const FileOptions& file_options, Logger* info_log,
|
||||
TableCache* table_cache,
|
||||
@@ -140,6 +146,57 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<BlobFileMetaData> GetBlobFileMetaData(
|
||||
uint64_t blob_file_number) const {
|
||||
auto changed_it = changed_blob_files_.find(blob_file_number);
|
||||
if (changed_it != changed_blob_files_.end()) {
|
||||
const auto& meta = changed_it->second;
|
||||
assert(meta);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
assert(base_vstorage_);
|
||||
|
||||
const auto& base_blob_files = base_vstorage_->GetBlobFiles();
|
||||
|
||||
auto base_it = base_blob_files.find(blob_file_number);
|
||||
if (base_it != base_blob_files.end()) {
|
||||
const auto& meta = base_it->second;
|
||||
assert(meta);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
return std::shared_ptr<BlobFileMetaData>();
|
||||
}
|
||||
|
||||
Status CheckConsistencyOfOldestBlobFileReference(
|
||||
const VersionStorageInfo* vstorage, uint64_t blob_file_number) const {
|
||||
assert(vstorage);
|
||||
|
||||
// TODO: remove this check once we actually start recoding metadata for
|
||||
// blob files in the MANIFEST.
|
||||
if (vstorage->GetBlobFiles().empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
if (blob_file_number == kInvalidBlobFileNumber) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
const auto meta = GetBlobFileMetaData(blob_file_number);
|
||||
if (!meta) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_number
|
||||
<< " is not part of this version";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CheckConsistency(VersionStorageInfo* vstorage) {
|
||||
#ifdef NDEBUG
|
||||
if (!vstorage->force_consistency_checks()) {
|
||||
@@ -148,10 +205,31 @@ class VersionBuilder::Rep {
|
||||
return Status::OK();
|
||||
}
|
||||
#endif
|
||||
// make sure the files are sorted correctly
|
||||
// Make sure the files are sorted correctly and that the oldest blob file
|
||||
// reference for each table file points to a valid blob file in this
|
||||
// version.
|
||||
for (int level = 0; level < num_levels_; level++) {
|
||||
auto& level_files = vstorage->LevelFiles(level);
|
||||
|
||||
if (level_files.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
assert(level_files[0]);
|
||||
Status s = CheckConsistencyOfOldestBlobFileReference(
|
||||
vstorage, level_files[0]->oldest_blob_file_number);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < level_files.size(); i++) {
|
||||
assert(level_files[i]);
|
||||
s = CheckConsistencyOfOldestBlobFileReference(
|
||||
vstorage, level_files[i]->oldest_blob_file_number);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
auto f1 = level_files[i - 1];
|
||||
auto f2 = level_files[i];
|
||||
#ifndef NDEBUG
|
||||
@@ -217,6 +295,23 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that all blob files in the version have non-garbage data.
|
||||
const auto& blob_files = vstorage->GetBlobFiles();
|
||||
for (const auto& pair : blob_files) {
|
||||
const auto& blob_file_meta = pair.second;
|
||||
assert(blob_file_meta);
|
||||
|
||||
if (blob_file_meta->GetGarbageBlobCount() >=
|
||||
blob_file_meta->GetTotalBlobCount()) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_meta->GetBlobFileNumber()
|
||||
<< " consists entirely of garbage";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -282,6 +377,56 @@ class VersionBuilder::Rep {
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ApplyBlobFileAddition(const BlobFileAddition& blob_file_addition) {
|
||||
const uint64_t blob_file_number = blob_file_addition.GetBlobFileNumber();
|
||||
|
||||
auto meta = GetBlobFileMetaData(blob_file_number);
|
||||
if (meta) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_number << " already added";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
auto shared_meta = std::make_shared<SharedBlobFileMetaData>(
|
||||
blob_file_number, blob_file_addition.GetTotalBlobCount(),
|
||||
blob_file_addition.GetTotalBlobBytes(),
|
||||
blob_file_addition.GetChecksumMethod(),
|
||||
blob_file_addition.GetChecksumValue());
|
||||
|
||||
constexpr uint64_t garbage_blob_count = 0;
|
||||
constexpr uint64_t garbage_blob_bytes = 0;
|
||||
auto new_meta = std::make_shared<BlobFileMetaData>(
|
||||
std::move(shared_meta), garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
changed_blob_files_.emplace(blob_file_number, std::move(new_meta));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status ApplyBlobFileGarbage(const BlobFileGarbage& blob_file_garbage) {
|
||||
const uint64_t blob_file_number = blob_file_garbage.GetBlobFileNumber();
|
||||
|
||||
auto meta = GetBlobFileMetaData(blob_file_number);
|
||||
if (!meta) {
|
||||
std::ostringstream oss;
|
||||
oss << "Blob file #" << blob_file_number << " not found";
|
||||
|
||||
return Status::Corruption("VersionBuilder", oss.str());
|
||||
}
|
||||
|
||||
assert(meta->GetBlobFileNumber() == blob_file_number);
|
||||
|
||||
auto new_meta = std::make_shared<BlobFileMetaData>(
|
||||
meta->GetSharedMeta(),
|
||||
meta->GetGarbageBlobCount() + blob_file_garbage.GetGarbageBlobCount(),
|
||||
meta->GetGarbageBlobBytes() + blob_file_garbage.GetGarbageBlobBytes());
|
||||
|
||||
changed_blob_files_[blob_file_number] = std::move(new_meta);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Apply all of the edits in *edit to the current state.
|
||||
Status Apply(VersionEdit* edit) {
|
||||
Status s = CheckConsistency(base_vstorage_);
|
||||
@@ -333,9 +478,104 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add new blob files
|
||||
for (const auto& blob_file_addition : edit->GetBlobFileAdditions()) {
|
||||
s = ApplyBlobFileAddition(blob_file_addition);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase the amount of garbage for blob files affected by GC
|
||||
for (const auto& blob_file_garbage : edit->GetBlobFileGarbages()) {
|
||||
s = ApplyBlobFileGarbage(blob_file_garbage);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
void AddBlobFileIfNeeded(
|
||||
VersionStorageInfo* vstorage,
|
||||
const std::shared_ptr<BlobFileMetaData>& meta) const {
|
||||
assert(vstorage);
|
||||
assert(meta);
|
||||
|
||||
if (meta->GetGarbageBlobCount() < meta->GetTotalBlobCount()) {
|
||||
vstorage->AddBlobFile(meta);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the blob file metadata from the base version with the changes (edits)
|
||||
// applied, and save the result into *vstorage.
|
||||
void SaveBlobFilesTo(VersionStorageInfo* vstorage) const {
|
||||
assert(base_vstorage_);
|
||||
assert(vstorage);
|
||||
|
||||
const auto& base_blob_files = base_vstorage_->GetBlobFiles();
|
||||
auto base_it = base_blob_files.begin();
|
||||
const auto base_it_end = base_blob_files.end();
|
||||
|
||||
auto changed_it = changed_blob_files_.begin();
|
||||
const auto changed_it_end = changed_blob_files_.end();
|
||||
|
||||
while (base_it != base_it_end && changed_it != changed_it_end) {
|
||||
const uint64_t base_blob_file_number = base_it->first;
|
||||
const uint64_t changed_blob_file_number = changed_it->first;
|
||||
|
||||
const auto& base_meta = base_it->second;
|
||||
const auto& changed_meta = changed_it->second;
|
||||
|
||||
assert(base_meta);
|
||||
assert(changed_meta);
|
||||
|
||||
if (base_blob_file_number < changed_blob_file_number) {
|
||||
assert(base_meta->GetGarbageBlobCount() <
|
||||
base_meta->GetTotalBlobCount());
|
||||
|
||||
vstorage->AddBlobFile(base_meta);
|
||||
|
||||
++base_it;
|
||||
} else if (changed_blob_file_number < base_blob_file_number) {
|
||||
AddBlobFileIfNeeded(vstorage, changed_meta);
|
||||
|
||||
++changed_it;
|
||||
} else {
|
||||
assert(base_blob_file_number == changed_blob_file_number);
|
||||
assert(base_meta->GetSharedMeta() == changed_meta->GetSharedMeta());
|
||||
assert(base_meta->GetGarbageBlobCount() <=
|
||||
changed_meta->GetGarbageBlobCount());
|
||||
assert(base_meta->GetGarbageBlobBytes() <=
|
||||
changed_meta->GetGarbageBlobBytes());
|
||||
|
||||
AddBlobFileIfNeeded(vstorage, changed_meta);
|
||||
|
||||
++base_it;
|
||||
++changed_it;
|
||||
}
|
||||
}
|
||||
|
||||
while (base_it != base_it_end) {
|
||||
const auto& base_meta = base_it->second;
|
||||
assert(base_meta);
|
||||
assert(base_meta->GetGarbageBlobCount() < base_meta->GetTotalBlobCount());
|
||||
|
||||
vstorage->AddBlobFile(base_meta);
|
||||
++base_it;
|
||||
}
|
||||
|
||||
while (changed_it != changed_it_end) {
|
||||
const auto& changed_meta = changed_it->second;
|
||||
assert(changed_meta);
|
||||
|
||||
AddBlobFileIfNeeded(vstorage, changed_meta);
|
||||
++changed_it;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the current state in *v.
|
||||
Status SaveTo(VersionStorageInfo* vstorage) {
|
||||
Status s = CheckConsistency(base_vstorage_);
|
||||
@@ -390,6 +630,8 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
}
|
||||
|
||||
SaveBlobFilesTo(vstorage);
|
||||
|
||||
s = CheckConsistency(vstorage);
|
||||
return s;
|
||||
}
|
||||
@@ -405,7 +647,7 @@ class VersionBuilder::Rep {
|
||||
size_t max_load = port::kMaxSizet;
|
||||
|
||||
if (!always_load) {
|
||||
// If it is initial loading and not set to always laoding all the
|
||||
// If it is initial loading and not set to always loading all the
|
||||
// files, we only load up to kInitialLoadLimit files, to limit the
|
||||
// time reopening the DB.
|
||||
const size_t kInitialLoadLimit = 16;
|
||||
@@ -507,16 +749,7 @@ VersionBuilder::VersionBuilder(const FileOptions& file_options,
|
||||
Logger* info_log)
|
||||
: rep_(new Rep(file_options, info_log, table_cache, base_vstorage)) {}
|
||||
|
||||
VersionBuilder::~VersionBuilder() { delete rep_; }
|
||||
|
||||
Status VersionBuilder::CheckConsistency(VersionStorageInfo* vstorage) {
|
||||
return rep_->CheckConsistency(vstorage);
|
||||
}
|
||||
|
||||
Status VersionBuilder::CheckConsistencyForDeletes(VersionEdit* edit,
|
||||
uint64_t number, int level) {
|
||||
return rep_->CheckConsistencyForDeletes(edit, number, level);
|
||||
}
|
||||
VersionBuilder::~VersionBuilder() = default;
|
||||
|
||||
bool VersionBuilder::CheckConsistencyForNumLevels() {
|
||||
return rep_->CheckConsistencyForNumLevels();
|
||||
@@ -537,9 +770,26 @@ Status VersionBuilder::LoadTableHandlers(
|
||||
is_initial_load, prefix_extractor);
|
||||
}
|
||||
|
||||
void VersionBuilder::MaybeAddFile(VersionStorageInfo* vstorage, int level,
|
||||
FileMetaData* f) {
|
||||
rep_->MaybeAddFile(vstorage, level, f);
|
||||
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->table_cache(),
|
||||
cfd->current()->storage_info(), cfd->ioptions()->info_log)),
|
||||
version_(cfd->current()) {
|
||||
version_->Ref();
|
||||
}
|
||||
|
||||
BaseReferencedVersionBuilder::BaseReferencedVersionBuilder(
|
||||
ColumnFamilyData* cfd, Version* v)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->table_cache(),
|
||||
v->storage_info(), cfd->ioptions()->info_log)),
|
||||
version_(v) {
|
||||
assert(version_ != cfd->current());
|
||||
}
|
||||
|
||||
BaseReferencedVersionBuilder::~BaseReferencedVersionBuilder() {
|
||||
version_->Unref();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+22
-5
@@ -8,6 +8,9 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
|
||||
@@ -18,6 +21,8 @@ class VersionStorageInfo;
|
||||
class VersionEdit;
|
||||
struct FileMetaData;
|
||||
class InternalStats;
|
||||
class Version;
|
||||
class ColumnFamilyData;
|
||||
|
||||
// A helper class so we can efficiently apply a whole sequence
|
||||
// of edits to a particular state without creating intermediate
|
||||
@@ -27,9 +32,7 @@ class VersionBuilder {
|
||||
VersionBuilder(const FileOptions& file_options, TableCache* table_cache,
|
||||
VersionStorageInfo* base_vstorage, Logger* info_log = nullptr);
|
||||
~VersionBuilder();
|
||||
Status CheckConsistency(VersionStorageInfo* vstorage);
|
||||
Status CheckConsistencyForDeletes(VersionEdit* edit, uint64_t number,
|
||||
int level);
|
||||
|
||||
bool CheckConsistencyForNumLevels();
|
||||
Status Apply(VersionEdit* edit);
|
||||
Status SaveTo(VersionStorageInfo* vstorage);
|
||||
@@ -37,11 +40,25 @@ class VersionBuilder {
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load,
|
||||
const SliceTransform* prefix_extractor);
|
||||
void MaybeAddFile(VersionStorageInfo* vstorage, int level, FileMetaData* f);
|
||||
|
||||
private:
|
||||
class Rep;
|
||||
Rep* rep_;
|
||||
std::unique_ptr<Rep> rep_;
|
||||
};
|
||||
|
||||
// A wrapper of version builder which references the current version in
|
||||
// constructor and unref it in the destructor.
|
||||
// Both of the constructor and destructor need to be called inside DB Mutex.
|
||||
class BaseReferencedVersionBuilder {
|
||||
public:
|
||||
explicit BaseReferencedVersionBuilder(ColumnFamilyData* cfd);
|
||||
BaseReferencedVersionBuilder(ColumnFamilyData* cfd, Version* v);
|
||||
~BaseReferencedVersionBuilder();
|
||||
VersionBuilder* version_builder() const { return version_builder_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<VersionBuilder> version_builder_;
|
||||
Version* version_;
|
||||
};
|
||||
|
||||
extern bool NewestFirstBySeqNo(FileMetaData* a, FileMetaData* b);
|
||||
|
||||
+475
-2
@@ -3,6 +3,8 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "db/version_edit.h"
|
||||
#include "db/version_set.h"
|
||||
@@ -57,12 +59,13 @@ class VersionBuilderTest : public testing::Test {
|
||||
SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100,
|
||||
uint64_t num_entries = 0, uint64_t num_deletions = 0,
|
||||
bool sampled = false, SequenceNumber smallest_seqno = 0,
|
||||
SequenceNumber largest_seqno = 0) {
|
||||
SequenceNumber largest_seqno = 0,
|
||||
uint64_t oldest_blob_file_number = kInvalidBlobFileNumber) {
|
||||
assert(level < vstorage_.num_levels());
|
||||
FileMetaData* f = new FileMetaData(
|
||||
file_number, path_id, file_size, GetInternalKey(smallest, smallest_seq),
|
||||
GetInternalKey(largest, largest_seq), smallest_seqno, largest_seqno,
|
||||
/* marked_for_compact */ false, kInvalidBlobFileNumber,
|
||||
/* marked_for_compact */ false, oldest_blob_file_number,
|
||||
kUnknownOldestAncesterTime, kUnknownFileCreationTime,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
f->compensated_file_size = file_size;
|
||||
@@ -75,6 +78,34 @@ class VersionBuilderTest : public testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
void AddBlob(uint64_t blob_file_number, uint64_t total_blob_count,
|
||||
uint64_t total_blob_bytes, std::string checksum_method,
|
||||
std::string checksum_value, uint64_t garbage_blob_count,
|
||||
uint64_t garbage_blob_bytes) {
|
||||
auto shared_meta = std::make_shared<SharedBlobFileMetaData>(
|
||||
blob_file_number, total_blob_count, total_blob_bytes,
|
||||
std::move(checksum_method), std::move(checksum_value));
|
||||
auto meta = std::make_shared<BlobFileMetaData>(
|
||||
std::move(shared_meta), garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
vstorage_.AddBlobFile(std::move(meta));
|
||||
}
|
||||
|
||||
static std::shared_ptr<BlobFileMetaData> GetBlobFileMetaData(
|
||||
const VersionStorageInfo::BlobFiles& blob_files,
|
||||
uint64_t blob_file_number) {
|
||||
const auto it = blob_files.find(blob_file_number);
|
||||
|
||||
if (it == blob_files.end()) {
|
||||
return std::shared_ptr<BlobFileMetaData>();
|
||||
}
|
||||
|
||||
const auto& meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
void UpdateVersionStorageInfo() {
|
||||
vstorage_.UpdateFilesByCompactionPri(ioptions_.compaction_pri);
|
||||
vstorage_.UpdateNumNonEmptyLevels();
|
||||
@@ -319,6 +350,448 @@ TEST_F(VersionBuilderTest, ApplyDeleteAndSaveTo) {
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileAddition) {
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
constexpr bool force_consistency_checks = false;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
const auto& new_blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(new_blob_files.size(), 1);
|
||||
|
||||
const auto new_meta = GetBlobFileMetaData(new_blob_files, blob_file_number);
|
||||
|
||||
ASSERT_NE(new_meta, nullptr);
|
||||
ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(), 0);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(), 0);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyInBase) {
|
||||
// Attempt to add a blob file that is already present in the base version.
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr uint64_t garbage_blob_count = 123;
|
||||
constexpr uint64_t garbage_blob_bytes = 456789;
|
||||
|
||||
AddBlob(blob_file_number, total_blob_count, total_blob_bytes, checksum_method,
|
||||
checksum_value, garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
const Status s = builder.Apply(&edit);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 already added"));
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileAdditionAlreadyApplied) {
|
||||
// Attempt to add the same blob file twice using version edits.
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
const Status s = builder.Apply(&edit);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 already added"));
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileInBase) {
|
||||
// Increase the amount of garbage for a blob file present in the base version.
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr uint64_t garbage_blob_count = 123;
|
||||
constexpr uint64_t garbage_blob_bytes = 456789;
|
||||
|
||||
AddBlob(blob_file_number, total_blob_count, total_blob_bytes, checksum_method,
|
||||
checksum_value, garbage_blob_count, garbage_blob_bytes);
|
||||
|
||||
const auto meta =
|
||||
GetBlobFileMetaData(vstorage_.GetBlobFiles(), blob_file_number);
|
||||
ASSERT_NE(meta, nullptr);
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
constexpr uint64_t new_garbage_blob_count = 456;
|
||||
constexpr uint64_t new_garbage_blob_bytes = 111111;
|
||||
|
||||
edit.AddBlobFileGarbage(blob_file_number, new_garbage_blob_count,
|
||||
new_garbage_blob_bytes);
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
constexpr bool force_consistency_checks = false;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
const auto& new_blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(new_blob_files.size(), 1);
|
||||
|
||||
const auto new_meta = GetBlobFileMetaData(new_blob_files, blob_file_number);
|
||||
|
||||
ASSERT_NE(new_meta, nullptr);
|
||||
ASSERT_EQ(new_meta->GetSharedMeta(), meta->GetSharedMeta());
|
||||
ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(),
|
||||
garbage_blob_count + new_garbage_blob_count);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(),
|
||||
garbage_blob_bytes + new_garbage_blob_bytes);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileAdditionApplied) {
|
||||
// Increase the amount of garbage for a blob file added using a version edit.
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit addition;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t total_blob_count = 5678;
|
||||
constexpr uint64_t total_blob_bytes = 999999;
|
||||
constexpr char checksum_method[] = "SHA1";
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
|
||||
addition.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
ASSERT_OK(builder.Apply(&addition));
|
||||
|
||||
constexpr uint64_t garbage_blob_count = 123;
|
||||
constexpr uint64_t garbage_blob_bytes = 456789;
|
||||
|
||||
VersionEdit garbage;
|
||||
|
||||
garbage.AddBlobFileGarbage(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
ASSERT_OK(builder.Apply(&garbage));
|
||||
|
||||
constexpr bool force_consistency_checks = false;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
const auto& new_blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(new_blob_files.size(), 1);
|
||||
|
||||
const auto new_meta = GetBlobFileMetaData(new_blob_files, blob_file_number);
|
||||
|
||||
ASSERT_NE(new_meta, nullptr);
|
||||
ASSERT_EQ(new_meta->GetBlobFileNumber(), blob_file_number);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobCount(), total_blob_count);
|
||||
ASSERT_EQ(new_meta->GetTotalBlobBytes(), total_blob_bytes);
|
||||
ASSERT_EQ(new_meta->GetChecksumMethod(), checksum_method);
|
||||
ASSERT_EQ(new_meta->GetChecksumValue(), checksum_value);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobCount(), garbage_blob_count);
|
||||
ASSERT_EQ(new_meta->GetGarbageBlobBytes(), garbage_blob_bytes);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, ApplyBlobFileGarbageFileNotFound) {
|
||||
// Attempt to increase the amount of garbage for a blob file that is
|
||||
// neither in the base version, nor was it added using a version edit.
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1234;
|
||||
constexpr uint64_t garbage_blob_count = 5678;
|
||||
constexpr uint64_t garbage_blob_bytes = 999999;
|
||||
|
||||
edit.AddBlobFileGarbage(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
|
||||
const Status s = builder.Apply(&edit);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "Blob file #1234 not found"));
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, SaveBlobFilesTo) {
|
||||
// Add three blob files to base version.
|
||||
for (uint64_t i = 1; i <= 3; ++i) {
|
||||
const uint64_t blob_file_number = i;
|
||||
const uint64_t total_blob_count = i * 1000;
|
||||
const uint64_t total_blob_bytes = i * 1000000;
|
||||
const uint64_t garbage_blob_count = i * 100;
|
||||
const uint64_t garbage_blob_bytes = i * 20000;
|
||||
|
||||
AddBlob(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(), garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
}
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
// Add some garbage to the second and third blob files. The second blob file
|
||||
// remains valid since it does not consist entirely of garbage yet. The third
|
||||
// blob file is all garbage after the edit and will not be part of the new
|
||||
// version.
|
||||
edit.AddBlobFileGarbage(/* blob_file_number */ 2,
|
||||
/* garbage_blob_count */ 200,
|
||||
/* garbage_blob_bytes */ 100000);
|
||||
edit.AddBlobFileGarbage(/* blob_file_number */ 3,
|
||||
/* garbage_blob_count */ 2700,
|
||||
/* garbage_blob_bytes */ 2940000);
|
||||
|
||||
// Add a fourth blob file.
|
||||
edit.AddBlobFile(/* blob_file_number */ 4, /* total_blob_count */ 4000,
|
||||
/* total_blob_bytes */ 4000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string());
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
constexpr bool force_consistency_checks = false;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
const auto& new_blob_files = new_vstorage.GetBlobFiles();
|
||||
ASSERT_EQ(new_blob_files.size(), 3);
|
||||
|
||||
const auto meta1 = GetBlobFileMetaData(new_blob_files, 1);
|
||||
|
||||
ASSERT_NE(meta1, nullptr);
|
||||
ASSERT_EQ(meta1->GetBlobFileNumber(), 1);
|
||||
ASSERT_EQ(meta1->GetTotalBlobCount(), 1000);
|
||||
ASSERT_EQ(meta1->GetTotalBlobBytes(), 1000000);
|
||||
ASSERT_EQ(meta1->GetGarbageBlobCount(), 100);
|
||||
ASSERT_EQ(meta1->GetGarbageBlobBytes(), 20000);
|
||||
|
||||
const auto meta2 = GetBlobFileMetaData(new_blob_files, 2);
|
||||
|
||||
ASSERT_NE(meta2, nullptr);
|
||||
ASSERT_EQ(meta2->GetBlobFileNumber(), 2);
|
||||
ASSERT_EQ(meta2->GetTotalBlobCount(), 2000);
|
||||
ASSERT_EQ(meta2->GetTotalBlobBytes(), 2000000);
|
||||
ASSERT_EQ(meta2->GetGarbageBlobCount(), 400);
|
||||
ASSERT_EQ(meta2->GetGarbageBlobBytes(), 140000);
|
||||
|
||||
const auto meta4 = GetBlobFileMetaData(new_blob_files, 4);
|
||||
|
||||
ASSERT_NE(meta4, nullptr);
|
||||
ASSERT_EQ(meta4->GetBlobFileNumber(), 4);
|
||||
ASSERT_EQ(meta4->GetTotalBlobCount(), 4000);
|
||||
ASSERT_EQ(meta4->GetTotalBlobBytes(), 4000000);
|
||||
ASSERT_EQ(meta4->GetGarbageBlobCount(), 0);
|
||||
ASSERT_EQ(meta4->GetGarbageBlobBytes(), 0);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFiles) {
|
||||
// Initialize base version. The first table file points to a valid blob file
|
||||
// in this version; the second one does not refer to any blob files.
|
||||
|
||||
Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150",
|
||||
/* largest */ "200", /* file_size */ 100,
|
||||
/* path_id */ 0, /* smallest_seq */ 100, /* largest_seq */ 100,
|
||||
/* num_entries */ 0, /* num_deletions */ 0,
|
||||
/* sampled */ false, /* smallest_seqno */ 100, /* largest_seqno */ 100,
|
||||
/* oldest_blob_file_number */ 16);
|
||||
Add(/* level */ 1, /* file_number */ 23, /* smallest */ "201",
|
||||
/* largest */ "300", /* file_size */ 100,
|
||||
/* path_id */ 0, /* smallest_seq */ 200, /* largest_seq */ 200,
|
||||
/* num_entries */ 0, /* num_deletions */ 0,
|
||||
/* sampled */ false, /* smallest_seqno */ 200, /* largest_seqno */ 200,
|
||||
kInvalidBlobFileNumber);
|
||||
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
/* garbage_blob_count */ 500, /* garbage_blob_bytes */ 300000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
// Add a new table file that points to the existing blob file, and add a
|
||||
// new table file--blob file pair.
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
VersionEdit edit;
|
||||
|
||||
edit.AddFile(/* level */ 1, /* file_number */ 606, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("701"),
|
||||
/* largest */ GetInternalKey("750"), /* smallest_seqno */ 200,
|
||||
/* largest_seqno */ 200, /* marked_for_compaction */ false,
|
||||
/* oldest_blob_file_number */ 16, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
|
||||
edit.AddFile(/* level */ 1, /* file_number */ 700, /* path_id */ 0,
|
||||
/* file_size */ 100, /* smallest */ GetInternalKey("801"),
|
||||
/* largest */ GetInternalKey("850"), /* smallest_seqno */ 200,
|
||||
/* largest_seqno */ 200, /* marked_for_compaction */ false,
|
||||
/* oldest_blob_file_number */ 1000, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
edit.AddBlobFile(/* blob_file_number */ 1000, /* total_blob_count */ 2000,
|
||||
/* total_blob_bytes */ 200000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string());
|
||||
|
||||
ASSERT_OK(builder.Apply(&edit));
|
||||
|
||||
// Save to a new version in order to trigger consistency checks.
|
||||
constexpr bool force_consistency_checks = true;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
ASSERT_OK(builder.SaveTo(&new_vstorage));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesNotInVersion) {
|
||||
// Initialize base version. The table file points to a blob file that is
|
||||
// not in this version.
|
||||
|
||||
Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150",
|
||||
/* largest */ "200", /* file_size */ 100,
|
||||
/* path_id */ 0, /* smallest_seq */ 100, /* largest_seq */ 100,
|
||||
/* num_entries */ 0, /* num_deletions */ 0,
|
||||
/* sampled */ false, /* smallest_seqno */ 100, /* largest_seqno */ 100,
|
||||
/* oldest_blob_file_number */ 256);
|
||||
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
/* garbage_blob_count */ 500, /* garbage_blob_bytes */ 300000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
// Save to a new version in order to trigger consistency checks.
|
||||
constexpr bool force_consistency_checks = true;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
const Status s = builder.SaveTo(&new_vstorage);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(
|
||||
std::strstr(s.getState(), "Blob file #256 is not part of this version"));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, CheckConsistencyForBlobFilesAllGarbage) {
|
||||
// Initialize base version. The table file points to a blob file that is
|
||||
// all garbage.
|
||||
|
||||
Add(/* level */ 1, /* file_number */ 1, /* smallest */ "150",
|
||||
/* largest */ "200", /* file_size */ 100,
|
||||
/* path_id */ 0, /* smallest_seq */ 100, /* largest_seq */ 100,
|
||||
/* num_entries */ 0, /* num_deletions */ 0,
|
||||
/* sampled */ false, /* smallest_seqno */ 100, /* largest_seqno */ 100,
|
||||
/* oldest_blob_file_number */ 16);
|
||||
|
||||
AddBlob(/* blob_file_number */ 16, /* total_blob_count */ 1000,
|
||||
/* total_blob_bytes */ 1000000,
|
||||
/* checksum_method */ std::string(),
|
||||
/* checksum_value */ std::string(),
|
||||
/* garbage_blob_count */ 1000, /* garbage_blob_bytes */ 1000000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
EnvOptions env_options;
|
||||
constexpr TableCache* table_cache = nullptr;
|
||||
VersionBuilder builder(env_options, table_cache, &vstorage_);
|
||||
|
||||
// Save to a new version in order to trigger consistency checks.
|
||||
constexpr bool force_consistency_checks = true;
|
||||
VersionStorageInfo new_vstorage(&icmp_, ucmp_, options_.num_levels,
|
||||
kCompactionStyleLevel, &vstorage_,
|
||||
force_consistency_checks);
|
||||
|
||||
const Status s = builder.SaveTo(&new_vstorage);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(
|
||||
std::strstr(s.getState(), "Blob file #16 consists entirely of garbage"));
|
||||
|
||||
UnrefFilesInVersion(&new_vstorage);
|
||||
}
|
||||
|
||||
TEST_F(VersionBuilderTest, EstimatedActiveKeys) {
|
||||
const uint32_t kTotalSamples = 20;
|
||||
const uint32_t kNumLevels = 5;
|
||||
|
||||
+106
-26
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "db/version_edit.h"
|
||||
|
||||
#include "db/blob_index.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/version_set.h"
|
||||
#include "logging/event_logger.h"
|
||||
#include "rocksdb/slice.h"
|
||||
@@ -22,8 +22,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
const std::string kUnknownFileChecksum("");
|
||||
// The unknown sst file checksum function name.
|
||||
const std::string kUnknownFileChecksumFuncName("Unknown");
|
||||
// Mask for an identified tag from the future which can be safely ignored.
|
||||
const uint32_t kTagSafeIgnoreMask = 1 << 13;
|
||||
|
||||
namespace {
|
||||
|
||||
// Tag numbers for serialized VersionEdit. These numbers are written to
|
||||
// disk and should not be changed. The number should be forward compatible so
|
||||
@@ -40,8 +40,6 @@ enum Tag : uint32_t {
|
||||
// 8 was used for large value refs
|
||||
kPrevLogNumber = 9,
|
||||
kMinLogNumberToKeep = 10,
|
||||
// Ignore-able field
|
||||
kDbId = kTagSafeIgnoreMask + 1,
|
||||
|
||||
// these are new formats divergent from open source leveldb
|
||||
kNewFile2 = 100,
|
||||
@@ -53,26 +51,38 @@ enum Tag : uint32_t {
|
||||
kMaxColumnFamily = 203,
|
||||
|
||||
kInAtomicGroup = 300,
|
||||
|
||||
// Mask for an unidentified tag from the future which can be safely ignored.
|
||||
kTagSafeIgnoreMask = 1 << 13,
|
||||
|
||||
// Forward compatible (aka ignorable) records
|
||||
kDbId,
|
||||
kBlobFileAddition,
|
||||
kBlobFileGarbage,
|
||||
};
|
||||
|
||||
enum CustomTag : uint32_t {
|
||||
enum NewFileCustomTag : uint32_t {
|
||||
kTerminate = 1, // The end of customized fields
|
||||
kNeedCompaction = 2,
|
||||
// Since Manifest is not entirely currently forward-compatible, and the only
|
||||
// forward-compatible part is the CutsomtTag of kNewFile, we currently encode
|
||||
// kMinLogNumberToKeep as part of a CustomTag as a hack. This should be
|
||||
// removed when manifest becomes forward-comptabile.
|
||||
// Since Manifest is not entirely forward-compatible, we currently encode
|
||||
// kMinLogNumberToKeep as part of NewFile as a hack. This should be removed
|
||||
// when manifest becomes forward-comptabile.
|
||||
kMinLogNumberToKeepHack = 3,
|
||||
kOldestBlobFileNumber = 4,
|
||||
kOldestAncesterTime = 5,
|
||||
kFileCreationTime = 6,
|
||||
kFileChecksum = 7,
|
||||
kFileChecksumFuncName = 8,
|
||||
kPathId = 65,
|
||||
|
||||
// If this bit for the custom tag is set, opening DB should fail if
|
||||
// we don't know this field.
|
||||
kCustomTagNonSafeIgnoreMask = 1 << 6,
|
||||
|
||||
// Forward incompatible (aka unignorable) fields
|
||||
kPathId,
|
||||
};
|
||||
// If this bit for the custom tag is set, opening DB should fail if
|
||||
// we don't know this field.
|
||||
uint32_t kCustomTagNonSafeIgnoreMask = 1 << 6;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
uint64_t PackFileNumberAndPathId(uint64_t number, uint64_t path_id) {
|
||||
assert(number <= kFileNumberMask);
|
||||
@@ -142,6 +152,8 @@ void VersionEdit::Clear() {
|
||||
has_last_sequence_ = false;
|
||||
deleted_files_.clear();
|
||||
new_files_.clear();
|
||||
blob_file_additions_.clear();
|
||||
blob_file_garbages_.clear();
|
||||
column_family_ = 0;
|
||||
is_column_family_add_ = false;
|
||||
is_column_family_drop_ = false;
|
||||
@@ -217,45 +229,45 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
// tag kNeedCompaction:
|
||||
// now only can take one char value 1 indicating need-compaction
|
||||
//
|
||||
PutVarint32(dst, CustomTag::kOldestAncesterTime);
|
||||
PutVarint32(dst, NewFileCustomTag::kOldestAncesterTime);
|
||||
std::string varint_oldest_ancester_time;
|
||||
PutVarint64(&varint_oldest_ancester_time, f.oldest_ancester_time);
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintOldestAncesterTime",
|
||||
&varint_oldest_ancester_time);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_oldest_ancester_time));
|
||||
|
||||
PutVarint32(dst, CustomTag::kFileCreationTime);
|
||||
PutVarint32(dst, NewFileCustomTag::kFileCreationTime);
|
||||
std::string varint_file_creation_time;
|
||||
PutVarint64(&varint_file_creation_time, f.file_creation_time);
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintFileCreationTime",
|
||||
&varint_file_creation_time);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_file_creation_time));
|
||||
|
||||
PutVarint32(dst, CustomTag::kFileChecksum);
|
||||
PutVarint32(dst, NewFileCustomTag::kFileChecksum);
|
||||
PutLengthPrefixedSlice(dst, Slice(f.file_checksum));
|
||||
|
||||
PutVarint32(dst, CustomTag::kFileChecksumFuncName);
|
||||
PutVarint32(dst, NewFileCustomTag::kFileChecksumFuncName);
|
||||
PutLengthPrefixedSlice(dst, Slice(f.file_checksum_func_name));
|
||||
|
||||
if (f.fd.GetPathId() != 0) {
|
||||
PutVarint32(dst, CustomTag::kPathId);
|
||||
PutVarint32(dst, NewFileCustomTag::kPathId);
|
||||
char p = static_cast<char>(f.fd.GetPathId());
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
if (f.marked_for_compaction) {
|
||||
PutVarint32(dst, CustomTag::kNeedCompaction);
|
||||
PutVarint32(dst, NewFileCustomTag::kNeedCompaction);
|
||||
char p = static_cast<char>(1);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
if (has_min_log_number_to_keep_ && !min_log_num_written) {
|
||||
PutVarint32(dst, CustomTag::kMinLogNumberToKeepHack);
|
||||
PutVarint32(dst, NewFileCustomTag::kMinLogNumberToKeepHack);
|
||||
std::string varint_log_number;
|
||||
PutFixed64(&varint_log_number, min_log_number_to_keep_);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_log_number));
|
||||
min_log_num_written = true;
|
||||
}
|
||||
if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
|
||||
PutVarint32(dst, CustomTag::kOldestBlobFileNumber);
|
||||
PutVarint32(dst, NewFileCustomTag::kOldestBlobFileNumber);
|
||||
std::string oldest_blob_file_number;
|
||||
PutVarint64(&oldest_blob_file_number, f.oldest_blob_file_number);
|
||||
PutLengthPrefixedSlice(dst, Slice(oldest_blob_file_number));
|
||||
@@ -263,7 +275,17 @@ bool VersionEdit::EncodeTo(std::string* dst) const {
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
|
||||
dst);
|
||||
|
||||
PutVarint32(dst, CustomTag::kTerminate);
|
||||
PutVarint32(dst, NewFileCustomTag::kTerminate);
|
||||
}
|
||||
|
||||
for (const auto& blob_file_addition : blob_file_additions_) {
|
||||
PutVarint32(dst, kBlobFileAddition);
|
||||
blob_file_addition.EncodeTo(dst);
|
||||
}
|
||||
|
||||
for (const auto& blob_file_garbage : blob_file_garbages_) {
|
||||
PutVarint32(dst, kBlobFileGarbage);
|
||||
blob_file_garbage.EncodeTo(dst);
|
||||
}
|
||||
|
||||
// 0 is default and does not need to be explicitly written
|
||||
@@ -319,9 +341,6 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
uint64_t file_size = 0;
|
||||
SequenceNumber smallest_seqno = 0;
|
||||
SequenceNumber largest_seqno = kMaxSequenceNumber;
|
||||
// Since this is the only forward-compatible part of the code, we hack new
|
||||
// extension into this record. When we do, we set this boolean to distinguish
|
||||
// the record from the normal NewFile records.
|
||||
if (GetLevel(input, &level, &msg) && GetVarint64(input, &number) &&
|
||||
GetVarint64(input, &file_size) && GetInternalKey(input, &f.smallest) &&
|
||||
GetInternalKey(input, &f.largest) &&
|
||||
@@ -571,6 +590,28 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
break;
|
||||
}
|
||||
|
||||
case kBlobFileAddition: {
|
||||
BlobFileAddition blob_file_addition;
|
||||
const Status s = blob_file_addition.DecodeFrom(&input);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
blob_file_additions_.emplace_back(blob_file_addition);
|
||||
break;
|
||||
}
|
||||
|
||||
case kBlobFileGarbage: {
|
||||
BlobFileGarbage blob_file_garbage;
|
||||
const Status s = blob_file_garbage.DecodeFrom(&input);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
blob_file_garbages_.emplace_back(blob_file_garbage);
|
||||
break;
|
||||
}
|
||||
|
||||
case kColumnFamily:
|
||||
if (!GetVarint32(&input, &column_family_)) {
|
||||
if (!msg) {
|
||||
@@ -700,6 +741,17 @@ std::string VersionEdit::DebugString(bool hex_key) const {
|
||||
r.append(" file_checksum_func_name: ");
|
||||
r.append(f.file_checksum_func_name);
|
||||
}
|
||||
|
||||
for (const auto& blob_file_addition : blob_file_additions_) {
|
||||
r.append("\n BlobFileAddition: ");
|
||||
r.append(blob_file_addition.DebugString());
|
||||
}
|
||||
|
||||
for (const auto& blob_file_garbage : blob_file_garbages_) {
|
||||
r.append("\n BlobFileGarbage: ");
|
||||
r.append(blob_file_garbage.DebugString());
|
||||
}
|
||||
|
||||
r.append("\n ColumnFamily: ");
|
||||
AppendNumberTo(&r, column_family_);
|
||||
if (is_column_family_add_) {
|
||||
@@ -782,6 +834,34 @@ std::string VersionEdit::DebugJSON(int edit_num, bool hex_key) const {
|
||||
jw.EndArray();
|
||||
}
|
||||
|
||||
if (!blob_file_additions_.empty()) {
|
||||
jw << "BlobFileAdditions";
|
||||
|
||||
jw.StartArray();
|
||||
|
||||
for (const auto& blob_file_addition : blob_file_additions_) {
|
||||
jw.StartArrayedObject();
|
||||
jw << blob_file_addition;
|
||||
jw.EndArrayedObject();
|
||||
}
|
||||
|
||||
jw.EndArray();
|
||||
}
|
||||
|
||||
if (!blob_file_garbages_.empty()) {
|
||||
jw << "BlobFileGarbages";
|
||||
|
||||
jw.StartArray();
|
||||
|
||||
for (const auto& blob_file_garbage : blob_file_garbages_) {
|
||||
jw.StartArrayedObject();
|
||||
jw << blob_file_garbage;
|
||||
jw.EndArrayedObject();
|
||||
}
|
||||
|
||||
jw.EndArray();
|
||||
}
|
||||
|
||||
jw << "ColumnFamily" << column_family_;
|
||||
|
||||
if (is_column_family_add_) {
|
||||
|
||||
+45
-6
@@ -13,6 +13,8 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
#include "db/blob/blob_file_garbage.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "memory/arena.h"
|
||||
#include "rocksdb/cache.h"
|
||||
@@ -24,7 +26,6 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class VersionSet;
|
||||
|
||||
constexpr uint64_t kFileNumberMask = 0x3FFFFFFFFFFFFFFF;
|
||||
constexpr uint64_t kInvalidBlobFileNumber = 0;
|
||||
constexpr uint64_t kUnknownOldestAncesterTime = 0;
|
||||
constexpr uint64_t kUnknownFileCreationTime = 0;
|
||||
|
||||
@@ -307,16 +308,16 @@ class VersionEdit {
|
||||
bool HasLastSequence() const { return has_last_sequence_; }
|
||||
SequenceNumber GetLastSequence() const { return last_sequence_; }
|
||||
|
||||
// Delete the specified "file" from the specified "level".
|
||||
// Delete the specified table file from the specified level.
|
||||
void DeleteFile(int level, uint64_t file) {
|
||||
deleted_files_.emplace(level, file);
|
||||
}
|
||||
|
||||
// Retrieve the files deleted as well as their associated levels.
|
||||
// Retrieve the table files deleted as well as their associated levels.
|
||||
using DeletedFiles = std::set<std::pair<int, uint64_t>>;
|
||||
const DeletedFiles& GetDeletedFiles() const { return deleted_files_; }
|
||||
|
||||
// Add the specified file at the specified level.
|
||||
// Add the specified table file at the specified level.
|
||||
// REQUIRES: This version has not been saved (see VersionSet::SaveTo)
|
||||
// REQUIRES: "smallest" and "largest" are smallest and largest keys in file
|
||||
// REQUIRES: "oldest_blob_file_number" is the number of the oldest blob file
|
||||
@@ -342,12 +343,45 @@ class VersionEdit {
|
||||
new_files_.emplace_back(level, f);
|
||||
}
|
||||
|
||||
// Retrieve the files added as well as their associated levels.
|
||||
// Retrieve the table files added as well as their associated levels.
|
||||
using NewFiles = std::vector<std::pair<int, FileMetaData>>;
|
||||
const NewFiles& GetNewFiles() const { return new_files_; }
|
||||
|
||||
// Add a new blob file.
|
||||
void AddBlobFile(uint64_t blob_file_number, uint64_t total_blob_count,
|
||||
uint64_t total_blob_bytes, std::string checksum_method,
|
||||
std::string checksum_value) {
|
||||
blob_file_additions_.emplace_back(
|
||||
blob_file_number, total_blob_count, total_blob_bytes,
|
||||
std::move(checksum_method), std::move(checksum_value));
|
||||
}
|
||||
|
||||
// Retrieve all the blob files added.
|
||||
using BlobFileAdditions = std::vector<BlobFileAddition>;
|
||||
const BlobFileAdditions& GetBlobFileAdditions() const {
|
||||
return blob_file_additions_;
|
||||
}
|
||||
|
||||
// Add garbage for an existing blob file. Note: intentionally broken English
|
||||
// follows.
|
||||
void AddBlobFileGarbage(uint64_t blob_file_number,
|
||||
uint64_t garbage_blob_count,
|
||||
uint64_t garbage_blob_bytes) {
|
||||
blob_file_garbages_.emplace_back(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
}
|
||||
|
||||
// Retrieve all the blob file garbage added.
|
||||
using BlobFileGarbages = std::vector<BlobFileGarbage>;
|
||||
const BlobFileGarbages& GetBlobFileGarbages() const {
|
||||
return blob_file_garbages_;
|
||||
}
|
||||
|
||||
// Number of edits
|
||||
size_t NumEntries() const { return new_files_.size() + deleted_files_.size(); }
|
||||
size_t NumEntries() const {
|
||||
return new_files_.size() + deleted_files_.size() +
|
||||
blob_file_additions_.size() + blob_file_garbages_.size();
|
||||
}
|
||||
|
||||
void SetColumnFamily(uint32_t column_family_id) {
|
||||
column_family_ = column_family_id;
|
||||
@@ -391,6 +425,8 @@ class VersionEdit {
|
||||
|
||||
private:
|
||||
friend class ReactiveVersionSet;
|
||||
friend class VersionEditHandler;
|
||||
friend class VersionEditHandlerPointInTime;
|
||||
friend class VersionSet;
|
||||
friend class Version;
|
||||
friend class AtomicGroupReadBuffer;
|
||||
@@ -421,6 +457,9 @@ class VersionEdit {
|
||||
DeletedFiles deleted_files_;
|
||||
NewFiles new_files_;
|
||||
|
||||
BlobFileAdditions blob_file_additions_;
|
||||
BlobFileGarbages blob_file_garbages_;
|
||||
|
||||
// Each version edit record should have column_family_ set
|
||||
// If it's not set, it is default (0)
|
||||
uint32_t column_family_ = 0;
|
||||
|
||||
@@ -0,0 +1,576 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/version_edit_handler.h"
|
||||
|
||||
#include "monitoring/persistent_stats_history.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
VersionEditHandler::VersionEditHandler(
|
||||
bool read_only, const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set, bool track_missing_files,
|
||||
bool no_error_if_table_files_missing)
|
||||
: read_only_(read_only),
|
||||
column_families_(column_families),
|
||||
status_(),
|
||||
version_set_(version_set),
|
||||
track_missing_files_(track_missing_files),
|
||||
no_error_if_table_files_missing_(no_error_if_table_files_missing),
|
||||
initialized_(false) {
|
||||
assert(version_set_ != nullptr);
|
||||
}
|
||||
|
||||
Status VersionEditHandler::Iterate(log::Reader& reader, std::string* db_id) {
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
size_t recovered_edits = 0;
|
||||
Status s = Initialize();
|
||||
while (reader.ReadRecord(&record, &scratch) && s.ok()) {
|
||||
VersionEdit edit;
|
||||
s = edit.DecodeFrom(record);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
if (edit.has_db_id_) {
|
||||
version_set_->db_id_ = edit.GetDbId();
|
||||
if (db_id != nullptr) {
|
||||
*db_id = version_set_->db_id_;
|
||||
}
|
||||
}
|
||||
s = read_buffer_.AddEdit(&edit);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
if (edit.is_in_atomic_group_) {
|
||||
if (read_buffer_.IsFull()) {
|
||||
for (auto& e : read_buffer_.replay_buffer()) {
|
||||
s = ApplyVersionEdit(e, &cfd);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
++recovered_edits;
|
||||
}
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
read_buffer_.Clear();
|
||||
}
|
||||
} else {
|
||||
s = ApplyVersionEdit(edit, &cfd);
|
||||
if (s.ok()) {
|
||||
++recovered_edits;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckIterationResult(reader, &s);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::Initialize() {
|
||||
Status s;
|
||||
if (!initialized_) {
|
||||
for (const auto& cf_desc : column_families_) {
|
||||
name_to_options_.emplace(cf_desc.name, cf_desc.options);
|
||||
}
|
||||
auto default_cf_iter = name_to_options_.find(kDefaultColumnFamilyName);
|
||||
if (default_cf_iter == name_to_options_.end()) {
|
||||
s = Status::InvalidArgument("Default column family not specified");
|
||||
}
|
||||
if (s.ok()) {
|
||||
VersionEdit default_cf_edit;
|
||||
default_cf_edit.AddColumnFamily(kDefaultColumnFamilyName);
|
||||
default_cf_edit.SetColumnFamily(0);
|
||||
ColumnFamilyData* cfd =
|
||||
CreateCfAndInit(default_cf_iter->second, default_cf_edit);
|
||||
assert(cfd != nullptr);
|
||||
#ifdef NDEBUG
|
||||
(void)cfd;
|
||||
#endif
|
||||
initialized_ = true;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::ApplyVersionEdit(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
Status s;
|
||||
if (edit.is_column_family_add_) {
|
||||
s = OnColumnFamilyAdd(edit, cfd);
|
||||
} else if (edit.is_column_family_drop_) {
|
||||
s = OnColumnFamilyDrop(edit, cfd);
|
||||
} else {
|
||||
s = OnNonCfOperation(edit, cfd);
|
||||
}
|
||||
if (s.ok()) {
|
||||
assert(cfd != nullptr);
|
||||
s = ExtractInfoFromVersionEdit(*cfd, edit);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::OnColumnFamilyAdd(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
Status s;
|
||||
if (cf_in_builders || cf_in_not_found) {
|
||||
s = Status::Corruption("MANIFEST adding the same column family twice: " +
|
||||
edit.column_family_name_);
|
||||
}
|
||||
if (s.ok()) {
|
||||
auto cf_options = name_to_options_.find(edit.column_family_name_);
|
||||
// implicitly add persistent_stats column family without requiring user
|
||||
// to specify
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
bool is_persistent_stats_column_family =
|
||||
edit.column_family_name_.compare(kPersistentStatsColumnFamilyName) == 0;
|
||||
if (cf_options == name_to_options_.end() &&
|
||||
!is_persistent_stats_column_family) {
|
||||
column_families_not_found_.emplace(edit.column_family_,
|
||||
edit.column_family_name_);
|
||||
} else {
|
||||
if (is_persistent_stats_column_family) {
|
||||
ColumnFamilyOptions cfo;
|
||||
OptimizeForPersistentStats(&cfo);
|
||||
tmp_cfd = CreateCfAndInit(cfo, edit);
|
||||
} else {
|
||||
tmp_cfd = CreateCfAndInit(cf_options->second, edit);
|
||||
}
|
||||
*cfd = tmp_cfd;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::OnColumnFamilyDrop(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
Status s;
|
||||
if (cf_in_builders) {
|
||||
tmp_cfd = DestroyCfAndCleanup(edit);
|
||||
} else if (cf_in_not_found) {
|
||||
column_families_not_found_.erase(edit.column_family_);
|
||||
} else {
|
||||
s = Status::Corruption("MANIFEST - dropping non-existing column family");
|
||||
}
|
||||
*cfd = tmp_cfd;
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::OnNonCfOperation(VersionEdit& edit,
|
||||
ColumnFamilyData** cfd) {
|
||||
bool cf_in_not_found = false;
|
||||
bool cf_in_builders = false;
|
||||
CheckColumnFamilyId(edit, &cf_in_not_found, &cf_in_builders);
|
||||
|
||||
assert(cfd != nullptr);
|
||||
*cfd = nullptr;
|
||||
Status s;
|
||||
if (!cf_in_not_found) {
|
||||
if (!cf_in_builders) {
|
||||
s = Status::Corruption(
|
||||
"MANIFEST record referencing unknown column family");
|
||||
}
|
||||
ColumnFamilyData* tmp_cfd = nullptr;
|
||||
if (s.ok()) {
|
||||
auto builder_iter = builders_.find(edit.column_family_);
|
||||
assert(builder_iter != builders_.end());
|
||||
tmp_cfd = version_set_->GetColumnFamilySet()->GetColumnFamily(
|
||||
edit.column_family_);
|
||||
assert(tmp_cfd != nullptr);
|
||||
s = MaybeCreateVersion(edit, tmp_cfd, /*force_create_version=*/false);
|
||||
if (s.ok()) {
|
||||
s = builder_iter->second->version_builder()->Apply(&edit);
|
||||
}
|
||||
}
|
||||
*cfd = tmp_cfd;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// TODO maybe cache the computation result
|
||||
bool VersionEditHandler::HasMissingFiles() const {
|
||||
bool ret = false;
|
||||
for (const auto& elem : cf_to_missing_files_) {
|
||||
const auto& missing_files = elem.second;
|
||||
if (!missing_files.empty()) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void VersionEditHandler::CheckColumnFamilyId(const VersionEdit& edit,
|
||||
bool* cf_in_not_found,
|
||||
bool* cf_in_builders) const {
|
||||
assert(cf_in_not_found != nullptr);
|
||||
assert(cf_in_builders != nullptr);
|
||||
// Not found means that user didn't supply that column
|
||||
// family option AND we encountered column family add
|
||||
// record. Once we encounter column family drop record,
|
||||
// we will delete the column family from
|
||||
// column_families_not_found.
|
||||
bool in_not_found = column_families_not_found_.find(edit.column_family_) !=
|
||||
column_families_not_found_.end();
|
||||
// in builders means that user supplied that column family
|
||||
// option AND that we encountered column family add record
|
||||
bool in_builders = builders_.find(edit.column_family_) != builders_.end();
|
||||
// They cannot both be true
|
||||
assert(!(in_not_found && in_builders));
|
||||
*cf_in_not_found = in_not_found;
|
||||
*cf_in_builders = in_builders;
|
||||
}
|
||||
|
||||
void VersionEditHandler::CheckIterationResult(const log::Reader& reader,
|
||||
Status* s) {
|
||||
assert(s != nullptr);
|
||||
if (!s->ok()) {
|
||||
read_buffer_.Clear();
|
||||
} else if (!version_edit_params_.has_log_number_ ||
|
||||
!version_edit_params_.has_next_file_number_ ||
|
||||
!version_edit_params_.has_last_sequence_) {
|
||||
std::string msg("no ");
|
||||
if (!version_edit_params_.has_log_number_) {
|
||||
msg.append("log_file_number, ");
|
||||
}
|
||||
if (!version_edit_params_.has_next_file_number_) {
|
||||
msg.append("next_file_number, ");
|
||||
}
|
||||
if (!version_edit_params_.has_last_sequence_) {
|
||||
msg.append("last_sequence, ");
|
||||
}
|
||||
msg = msg.substr(0, msg.size() - 2);
|
||||
msg.append(" entry in MANIFEST");
|
||||
*s = Status::Corruption(msg);
|
||||
}
|
||||
if (s->ok() && !read_only_ && !column_families_not_found_.empty()) {
|
||||
std::string msg;
|
||||
for (const auto& cf : column_families_not_found_) {
|
||||
msg.append(", ");
|
||||
msg.append(cf.second);
|
||||
}
|
||||
msg = msg.substr(2);
|
||||
*s = Status::InvalidArgument("Column families not opened: " + msg);
|
||||
}
|
||||
if (s->ok()) {
|
||||
version_set_->GetColumnFamilySet()->UpdateMaxColumnFamily(
|
||||
version_edit_params_.max_column_family_);
|
||||
version_set_->MarkMinLogNumberToKeep2PC(
|
||||
version_edit_params_.min_log_number_to_keep_);
|
||||
version_set_->MarkFileNumberUsed(version_edit_params_.prev_log_number_);
|
||||
version_set_->MarkFileNumberUsed(version_edit_params_.log_number_);
|
||||
for (auto* cfd : *(version_set_->GetColumnFamilySet())) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
if (!builder->CheckConsistencyForNumLevels()) {
|
||||
*s = Status::InvalidArgument(
|
||||
"db has more levels than options.num_levels");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s->ok()) {
|
||||
for (auto* cfd : *(version_set_->GetColumnFamilySet())) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (read_only_) {
|
||||
cfd->table_cache()->SetTablesAreImmortal();
|
||||
}
|
||||
*s = LoadTables(cfd, /*prefetch_index_and_filter_in_cache=*/false,
|
||||
/*is_initial_load=*/true);
|
||||
if (!s->ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s->ok()) {
|
||||
for (auto* cfd : *(version_set_->column_family_set_)) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
assert(cfd->initialized());
|
||||
VersionEdit edit;
|
||||
*s = MaybeCreateVersion(edit, cfd, /*force_create_version=*/true);
|
||||
if (!s->ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s->ok()) {
|
||||
version_set_->manifest_file_size_ = reader.GetReadOffset();
|
||||
assert(version_set_->manifest_file_size_ > 0);
|
||||
version_set_->next_file_number_.store(
|
||||
version_edit_params_.next_file_number_ + 1);
|
||||
version_set_->last_allocated_sequence_ =
|
||||
version_edit_params_.last_sequence_;
|
||||
version_set_->last_published_sequence_ =
|
||||
version_edit_params_.last_sequence_;
|
||||
version_set_->last_sequence_ = version_edit_params_.last_sequence_;
|
||||
version_set_->prev_log_number_ = version_edit_params_.prev_log_number_;
|
||||
}
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionEditHandler::CreateCfAndInit(
|
||||
const ColumnFamilyOptions& cf_options, const VersionEdit& edit) {
|
||||
ColumnFamilyData* cfd = version_set_->CreateColumnFamily(cf_options, &edit);
|
||||
assert(cfd != nullptr);
|
||||
cfd->set_initialized();
|
||||
assert(builders_.find(edit.column_family_) == builders_.end());
|
||||
builders_.emplace(edit.column_family_,
|
||||
VersionBuilderUPtr(new BaseReferencedVersionBuilder(cfd)));
|
||||
if (track_missing_files_) {
|
||||
cf_to_missing_files_.emplace(edit.column_family_,
|
||||
std::unordered_set<uint64_t>());
|
||||
}
|
||||
return cfd;
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionEditHandler::DestroyCfAndCleanup(
|
||||
const VersionEdit& edit) {
|
||||
auto builder_iter = builders_.find(edit.column_family_);
|
||||
assert(builder_iter != builders_.end());
|
||||
builders_.erase(builder_iter);
|
||||
if (track_missing_files_) {
|
||||
auto missing_files_iter = cf_to_missing_files_.find(edit.column_family_);
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
cf_to_missing_files_.erase(missing_files_iter);
|
||||
}
|
||||
ColumnFamilyData* ret =
|
||||
version_set_->GetColumnFamilySet()->GetColumnFamily(edit.column_family_);
|
||||
assert(ret != nullptr);
|
||||
if (ret->UnrefAndTryDelete()) {
|
||||
ret = nullptr;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::MaybeCreateVersion(const VersionEdit& /*edit*/,
|
||||
ColumnFamilyData* cfd,
|
||||
bool force_create_version) {
|
||||
assert(cfd->initialized());
|
||||
if (force_create_version) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
auto* v = new Version(cfd, version_set_, version_set_->file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
version_set_->current_version_number_++);
|
||||
builder->SaveTo(v->storage_info());
|
||||
// Install new version
|
||||
v->PrepareApply(*cfd->GetLatestMutableCFOptions(),
|
||||
!(version_set_->db_options_->skip_stats_update_on_db_open));
|
||||
version_set_->AppendVersion(cfd, v);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status VersionEditHandler::LoadTables(ColumnFamilyData* cfd,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load) {
|
||||
assert(cfd != nullptr);
|
||||
assert(!cfd->IsDropped());
|
||||
Status s;
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
assert(builder_iter->second != nullptr);
|
||||
VersionBuilder* builder = builder_iter->second->version_builder();
|
||||
assert(builder);
|
||||
s = builder->LoadTableHandlers(
|
||||
cfd->internal_stats(),
|
||||
version_set_->db_options_->max_file_opening_threads,
|
||||
prefetch_index_and_filter_in_cache, is_initial_load,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
if (s.IsPathNotFound() && no_error_if_table_files_missing_) {
|
||||
s = Status::OK();
|
||||
}
|
||||
if (!s.ok() && !version_set_->db_options_->paranoid_checks) {
|
||||
s = Status::OK();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionEditHandler::ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
const VersionEdit& edit) {
|
||||
Status s;
|
||||
if (cfd != nullptr) {
|
||||
if (edit.has_db_id_) {
|
||||
version_edit_params_.SetDBId(edit.db_id_);
|
||||
}
|
||||
if (edit.has_log_number_) {
|
||||
if (cfd->GetLogNumber() > edit.log_number_) {
|
||||
ROCKS_LOG_WARN(
|
||||
version_set_->db_options()->info_log,
|
||||
"MANIFEST corruption detected, but ignored - Log numbers in "
|
||||
"records NOT monotonically increasing");
|
||||
} else {
|
||||
cfd->SetLogNumber(edit.log_number_);
|
||||
version_edit_params_.SetLogNumber(edit.log_number_);
|
||||
}
|
||||
}
|
||||
if (edit.has_comparator_ &&
|
||||
edit.comparator_ != cfd->user_comparator()->Name()) {
|
||||
s = Status::InvalidArgument(
|
||||
cfd->user_comparator()->Name(),
|
||||
"does not match existing comparator " + edit.comparator_);
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
if (edit.has_prev_log_number_) {
|
||||
version_edit_params_.SetPrevLogNumber(edit.prev_log_number_);
|
||||
}
|
||||
if (edit.has_next_file_number_) {
|
||||
version_edit_params_.SetNextFile(edit.next_file_number_);
|
||||
}
|
||||
if (edit.has_max_column_family_) {
|
||||
version_edit_params_.SetMaxColumnFamily(edit.max_column_family_);
|
||||
}
|
||||
if (edit.has_min_log_number_to_keep_) {
|
||||
version_edit_params_.min_log_number_to_keep_ =
|
||||
std::max(version_edit_params_.min_log_number_to_keep_,
|
||||
edit.min_log_number_to_keep_);
|
||||
}
|
||||
if (edit.has_last_sequence_) {
|
||||
version_edit_params_.SetLastSequence(edit.last_sequence_);
|
||||
}
|
||||
if (!version_edit_params_.has_prev_log_number_) {
|
||||
version_edit_params_.SetPrevLogNumber(0);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
VersionEditHandlerPointInTime::VersionEditHandlerPointInTime(
|
||||
bool read_only, const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set)
|
||||
: VersionEditHandler(read_only, column_families, version_set,
|
||||
/*track_missing_files=*/true,
|
||||
/*no_error_if_table_files_missing=*/true) {}
|
||||
|
||||
VersionEditHandlerPointInTime::~VersionEditHandlerPointInTime() {
|
||||
for (const auto& elem : versions_) {
|
||||
delete elem.second;
|
||||
}
|
||||
versions_.clear();
|
||||
}
|
||||
|
||||
void VersionEditHandlerPointInTime::CheckIterationResult(
|
||||
const log::Reader& reader, Status* s) {
|
||||
VersionEditHandler::CheckIterationResult(reader, s);
|
||||
assert(s != nullptr);
|
||||
if (s->ok()) {
|
||||
for (auto* cfd : *(version_set_->column_family_set_)) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
assert(cfd->initialized());
|
||||
auto v_iter = versions_.find(cfd->GetID());
|
||||
if (v_iter != versions_.end()) {
|
||||
assert(v_iter->second != nullptr);
|
||||
|
||||
version_set_->AppendVersion(cfd, v_iter->second);
|
||||
versions_.erase(v_iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionEditHandlerPointInTime::DestroyCfAndCleanup(
|
||||
const VersionEdit& edit) {
|
||||
ColumnFamilyData* cfd = VersionEditHandler::DestroyCfAndCleanup(edit);
|
||||
auto v_iter = versions_.find(edit.column_family_);
|
||||
if (v_iter != versions_.end()) {
|
||||
delete v_iter->second;
|
||||
versions_.erase(v_iter);
|
||||
}
|
||||
return cfd;
|
||||
}
|
||||
|
||||
Status VersionEditHandlerPointInTime::MaybeCreateVersion(
|
||||
const VersionEdit& edit, ColumnFamilyData* cfd, bool force_create_version) {
|
||||
assert(cfd != nullptr);
|
||||
if (!force_create_version) {
|
||||
assert(edit.column_family_ == cfd->GetID());
|
||||
}
|
||||
auto missing_files_iter = cf_to_missing_files_.find(cfd->GetID());
|
||||
assert(missing_files_iter != cf_to_missing_files_.end());
|
||||
std::unordered_set<uint64_t>& missing_files = missing_files_iter->second;
|
||||
const bool prev_has_missing_files = !missing_files.empty();
|
||||
for (const auto& file : edit.GetDeletedFiles()) {
|
||||
uint64_t file_num = file.second;
|
||||
auto fiter = missing_files.find(file_num);
|
||||
if (fiter != missing_files.end()) {
|
||||
missing_files.erase(fiter);
|
||||
}
|
||||
}
|
||||
Status s;
|
||||
for (const auto& elem : edit.GetNewFiles()) {
|
||||
const FileMetaData& meta = elem.second;
|
||||
const FileDescriptor& fd = meta.fd;
|
||||
uint64_t file_num = fd.GetNumber();
|
||||
const std::string fpath =
|
||||
MakeTableFileName(cfd->ioptions()->cf_paths[0].path, file_num);
|
||||
s = version_set_->VerifyFileMetadata(fpath, meta);
|
||||
if (s.IsPathNotFound() || s.IsNotFound()) {
|
||||
missing_files.insert(file_num);
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
bool missing_info = !version_edit_params_.has_log_number_ ||
|
||||
!version_edit_params_.has_next_file_number_ ||
|
||||
!version_edit_params_.has_last_sequence_;
|
||||
|
||||
// Create version before apply edit
|
||||
if (!missing_info && ((!missing_files.empty() && !prev_has_missing_files) ||
|
||||
(missing_files.empty() && force_create_version))) {
|
||||
auto builder_iter = builders_.find(cfd->GetID());
|
||||
assert(builder_iter != builders_.end());
|
||||
auto* builder = builder_iter->second->version_builder();
|
||||
auto* version = new Version(cfd, version_set_, version_set_->file_options_,
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
version_set_->current_version_number_++);
|
||||
builder->SaveTo(version->storage_info());
|
||||
version->PrepareApply(
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
!version_set_->db_options_->skip_stats_update_on_db_open);
|
||||
auto v_iter = versions_.find(cfd->GetID());
|
||||
if (v_iter != versions_.end()) {
|
||||
delete v_iter->second;
|
||||
v_iter->second = version;
|
||||
} else {
|
||||
versions_.emplace(cfd->GetID(), version);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,123 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "db/version_builder.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "db/version_set.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
typedef std::unique_ptr<BaseReferencedVersionBuilder> VersionBuilderUPtr;
|
||||
|
||||
// A class used for scanning MANIFEST file.
|
||||
// VersionEditHandler reads a MANIFEST file, parses the version edits, and
|
||||
// builds the version set's in-memory state, e.g. the version storage info for
|
||||
// the versions of column families.
|
||||
// To use this class and its subclasses,
|
||||
// 1. Create an object of VersionEditHandler or its subclasses.
|
||||
// VersionEditHandler handler(read_only, column_families, version_set,
|
||||
// track_missing_files, ignore_missing_files);
|
||||
// 2. Status s = handler.Iterate(reader, &db_id);
|
||||
// 3. Check s and handle possible errors.
|
||||
//
|
||||
// Not thread-safe, external synchronization is necessary if an object of
|
||||
// VersionEditHandler is shared by multiple threads.
|
||||
class VersionEditHandler {
|
||||
public:
|
||||
explicit VersionEditHandler(
|
||||
bool read_only,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set, bool track_missing_files,
|
||||
bool ignore_missing_files);
|
||||
|
||||
virtual ~VersionEditHandler() {}
|
||||
|
||||
Status Iterate(log::Reader& reader, std::string* db_id);
|
||||
|
||||
const Status& status() const { return status_; }
|
||||
|
||||
bool HasMissingFiles() const;
|
||||
|
||||
protected:
|
||||
Status ApplyVersionEdit(VersionEdit& edit, ColumnFamilyData** cfd);
|
||||
|
||||
Status OnColumnFamilyAdd(VersionEdit& edit, ColumnFamilyData** cfd);
|
||||
|
||||
Status OnColumnFamilyDrop(VersionEdit& edit, ColumnFamilyData** cfd);
|
||||
|
||||
Status OnNonCfOperation(VersionEdit& edit, ColumnFamilyData** cfd);
|
||||
|
||||
Status Initialize();
|
||||
|
||||
void CheckColumnFamilyId(const VersionEdit& edit, bool* cf_in_not_found,
|
||||
bool* cf_in_builders) const;
|
||||
|
||||
virtual void CheckIterationResult(const log::Reader& reader, Status* s);
|
||||
|
||||
ColumnFamilyData* CreateCfAndInit(const ColumnFamilyOptions& cf_options,
|
||||
const VersionEdit& edit);
|
||||
|
||||
virtual ColumnFamilyData* DestroyCfAndCleanup(const VersionEdit& edit);
|
||||
|
||||
virtual Status MaybeCreateVersion(const VersionEdit& edit,
|
||||
ColumnFamilyData* cfd,
|
||||
bool force_create_version);
|
||||
|
||||
Status LoadTables(ColumnFamilyData* cfd,
|
||||
bool prefetch_index_and_filter_in_cache,
|
||||
bool is_initial_load);
|
||||
|
||||
const bool read_only_;
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families_;
|
||||
Status status_;
|
||||
VersionSet* version_set_;
|
||||
AtomicGroupReadBuffer read_buffer_;
|
||||
std::unordered_map<uint32_t, VersionBuilderUPtr> builders_;
|
||||
std::unordered_map<std::string, ColumnFamilyOptions> name_to_options_;
|
||||
std::unordered_map<uint32_t, std::string> column_families_not_found_;
|
||||
VersionEditParams version_edit_params_;
|
||||
const bool track_missing_files_;
|
||||
std::unordered_map<uint32_t, std::unordered_set<uint64_t>>
|
||||
cf_to_missing_files_;
|
||||
bool no_error_if_table_files_missing_;
|
||||
|
||||
private:
|
||||
Status ExtractInfoFromVersionEdit(ColumnFamilyData* cfd,
|
||||
const VersionEdit& edit);
|
||||
|
||||
bool initialized_;
|
||||
};
|
||||
|
||||
// A class similar to its base class, i.e. VersionEditHandler.
|
||||
// VersionEditHandlerPointInTime restores the versions to the most recent point
|
||||
// in time such that at this point, the version does not have missing files.
|
||||
//
|
||||
// Not thread-safe, external synchronization is necessary if an object of
|
||||
// VersionEditHandlerPointInTime is shared by multiple threads.
|
||||
class VersionEditHandlerPointInTime : public VersionEditHandler {
|
||||
public:
|
||||
VersionEditHandlerPointInTime(
|
||||
bool read_only,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
VersionSet* version_set);
|
||||
~VersionEditHandlerPointInTime() override;
|
||||
|
||||
protected:
|
||||
void CheckIterationResult(const log::Reader& reader, Status* s) override;
|
||||
ColumnFamilyData* DestroyCfAndCleanup(const VersionEdit& edit) override;
|
||||
Status MaybeCreateVersion(const VersionEdit& edit, ColumnFamilyData* cfd,
|
||||
bool force_create_version) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, Version*> versions_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -278,6 +279,36 @@ TEST_F(VersionEditTest, DbId) {
|
||||
TestEncodeDecode(edit);
|
||||
}
|
||||
|
||||
TEST_F(VersionEditTest, BlobFileAdditionAndGarbage) {
|
||||
VersionEdit edit;
|
||||
|
||||
const std::string checksum_method_prefix = "Hash";
|
||||
const std::string checksum_value_prefix = "Value";
|
||||
|
||||
for (uint64_t blob_file_number = 1; blob_file_number <= 10;
|
||||
++blob_file_number) {
|
||||
const uint64_t total_blob_count = blob_file_number << 10;
|
||||
const uint64_t total_blob_bytes = blob_file_number << 20;
|
||||
|
||||
std::string checksum_method(checksum_method_prefix);
|
||||
AppendNumberTo(&checksum_method, blob_file_number);
|
||||
|
||||
std::string checksum_value(checksum_value_prefix);
|
||||
AppendNumberTo(&checksum_value, blob_file_number);
|
||||
|
||||
edit.AddBlobFile(blob_file_number, total_blob_count, total_blob_bytes,
|
||||
checksum_method, checksum_value);
|
||||
|
||||
const uint64_t garbage_blob_count = total_blob_count >> 2;
|
||||
const uint64_t garbage_blob_bytes = total_blob_bytes >> 1;
|
||||
|
||||
edit.AddBlobFileGarbage(blob_file_number, garbage_blob_count,
|
||||
garbage_blob_bytes);
|
||||
}
|
||||
|
||||
TestEncodeDecode(edit);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+261
-51
@@ -29,6 +29,7 @@
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_builder.h"
|
||||
#include "db/version_edit_handler.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "file/read_write_util.h"
|
||||
@@ -955,8 +956,8 @@ class LevelIterator final : public InternalIterator {
|
||||
bool KeyReachedUpperBound(const Slice& internal_key) {
|
||||
return read_options_.iterate_upper_bound != nullptr &&
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
ExtractUserKey(internal_key),
|
||||
*read_options_.iterate_upper_bound) >= 0;
|
||||
ExtractUserKey(internal_key), /*a_has_ts=*/true,
|
||||
*read_options_.iterate_upper_bound, /*b_has_ts=*/false) >= 0;
|
||||
}
|
||||
|
||||
InternalIterator* NewFileIterator() {
|
||||
@@ -1202,28 +1203,6 @@ void LevelIterator::InitFileIterator(size_t new_file_index) {
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
// A wrapper of version builder which references the current version in
|
||||
// constructor and unref it in the destructor.
|
||||
// Both of the constructor and destructor need to be called inside DB Mutex.
|
||||
class BaseReferencedVersionBuilder {
|
||||
public:
|
||||
explicit BaseReferencedVersionBuilder(ColumnFamilyData* cfd)
|
||||
: version_builder_(new VersionBuilder(
|
||||
cfd->current()->version_set()->file_options(), cfd->table_cache(),
|
||||
cfd->current()->storage_info(), cfd->ioptions()->info_log)),
|
||||
version_(cfd->current()) {
|
||||
version_->Ref();
|
||||
}
|
||||
~BaseReferencedVersionBuilder() {
|
||||
version_->Unref();
|
||||
}
|
||||
VersionBuilder* version_builder() { return version_builder_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<VersionBuilder> version_builder_;
|
||||
Version* version_;
|
||||
};
|
||||
|
||||
Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
const FileMetaData* file_meta,
|
||||
const std::string* fname) const {
|
||||
@@ -1754,7 +1733,7 @@ Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset,
|
||||
version_number_(version_number) {}
|
||||
|
||||
void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
PinnableSlice* value, Status* status,
|
||||
PinnableSlice* value, std::string* timestamp, Status* status,
|
||||
MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq, bool* value_found,
|
||||
bool* key_exists, SequenceNumber* seq, ReadCallback* callback,
|
||||
@@ -1778,8 +1757,8 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
GetContext get_context(
|
||||
user_comparator(), merge_operator_, info_log_, db_statistics_,
|
||||
status->ok() ? GetContext::kNotFound : GetContext::kMerge, user_key,
|
||||
do_merge ? value : nullptr, value_found, merge_context, do_merge,
|
||||
max_covering_tombstone_seq, this->env_, seq,
|
||||
do_merge ? value : nullptr, do_merge ? timestamp : nullptr, value_found,
|
||||
merge_context, do_merge, max_covering_tombstone_seq, this->env_, seq,
|
||||
merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob,
|
||||
tracing_get_id);
|
||||
|
||||
@@ -1918,7 +1897,7 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
get_ctx.emplace_back(
|
||||
user_comparator(), merge_operator_, info_log_, db_statistics_,
|
||||
iter->s->ok() ? GetContext::kNotFound : GetContext::kMerge, iter->ukey,
|
||||
iter->value, nullptr, &(iter->merge_context), true,
|
||||
iter->value, iter->timestamp, nullptr, &(iter->merge_context), true,
|
||||
&iter->max_covering_tombstone_seq, this->env_, nullptr,
|
||||
merge_operator_ ? &pinned_iters_mgr : nullptr, callback, is_blob,
|
||||
tracing_mget_id);
|
||||
@@ -2620,6 +2599,19 @@ void VersionStorageInfo::AddFile(int level, FileMetaData* f, Logger* info_log) {
|
||||
level_files->push_back(f);
|
||||
}
|
||||
|
||||
void VersionStorageInfo::AddBlobFile(
|
||||
std::shared_ptr<BlobFileMetaData> blob_file_meta) {
|
||||
assert(blob_file_meta);
|
||||
|
||||
const uint64_t blob_file_number = blob_file_meta->GetBlobFileNumber();
|
||||
|
||||
auto it = blob_files_.lower_bound(blob_file_number);
|
||||
assert(it == blob_files_.end() || it->first != blob_file_number);
|
||||
|
||||
blob_files_.insert(
|
||||
it, BlobFiles::value_type(blob_file_number, std::move(blob_file_meta)));
|
||||
}
|
||||
|
||||
// Version::PrepareApply() need to be called before calling the function, or
|
||||
// following functions called:
|
||||
// 1. UpdateNumNonEmptyLevels();
|
||||
@@ -3462,6 +3454,21 @@ std::string Version::DebugString(bool hex, bool print_stats) const {
|
||||
r.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
const auto& blob_files = storage_info_.GetBlobFiles();
|
||||
if (!blob_files.empty()) {
|
||||
r.append("--- blob files --- version# ");
|
||||
AppendNumberTo(&r, version_number_);
|
||||
r.append(" ---\n");
|
||||
for (const auto& pair : blob_files) {
|
||||
const auto& blob_file_meta = pair.second;
|
||||
assert(blob_file_meta);
|
||||
|
||||
r.append(blob_file_meta->DebugString());
|
||||
r.push_back('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -3570,6 +3577,33 @@ VersionSet::~VersionSet() {
|
||||
obsolete_files_.clear();
|
||||
}
|
||||
|
||||
void VersionSet::Reset() {
|
||||
if (column_family_set_) {
|
||||
Cache* table_cache = column_family_set_->get_table_cache();
|
||||
WriteBufferManager* wbm = column_family_set_->write_buffer_manager();
|
||||
WriteController* wc = column_family_set_->write_controller();
|
||||
column_family_set_.reset(new ColumnFamilySet(dbname_, db_options_,
|
||||
file_options_, table_cache,
|
||||
wbm, wc, block_cache_tracer_));
|
||||
}
|
||||
db_id_.clear();
|
||||
next_file_number_.store(2);
|
||||
min_log_number_to_keep_2pc_.store(0);
|
||||
manifest_file_number_ = 0;
|
||||
options_file_number_ = 0;
|
||||
pending_manifest_file_number_ = 0;
|
||||
last_sequence_.store(0);
|
||||
last_allocated_sequence_.store(0);
|
||||
last_published_sequence_.store(0);
|
||||
prev_log_number_ = 0;
|
||||
descriptor_log_.reset();
|
||||
current_version_number_ = 0;
|
||||
manifest_writers_.clear();
|
||||
manifest_file_size_ = 0;
|
||||
obsolete_files_.clear();
|
||||
obsolete_manifests_.clear();
|
||||
}
|
||||
|
||||
void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
|
||||
Version* v) {
|
||||
// compute new compaction score
|
||||
@@ -3600,7 +3634,7 @@ void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
|
||||
|
||||
Status VersionSet::ProcessManifestWrites(
|
||||
std::deque<ManifestWriter>& writers, InstrumentedMutex* mu,
|
||||
Directory* db_directory, bool new_descriptor_log,
|
||||
FSDirectory* db_directory, bool new_descriptor_log,
|
||||
const ColumnFamilyOptions* new_cf_options) {
|
||||
assert(!writers.empty());
|
||||
ManifestWriter& first_writer = writers.front();
|
||||
@@ -3762,6 +3796,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
|
||||
uint64_t new_manifest_file_size = 0;
|
||||
Status s;
|
||||
IOStatus io_s;
|
||||
|
||||
assert(pending_manifest_file_number_ == 0);
|
||||
if (!descriptor_log_ ||
|
||||
@@ -3805,7 +3840,7 @@ Status VersionSet::ProcessManifestWrites(
|
||||
builder_guards.size() == versions.size());
|
||||
ColumnFamilyData* cfd = versions[i]->cfd_;
|
||||
s = builder_guards[i]->version_builder()->LoadTableHandlers(
|
||||
cfd->internal_stats(), cfd->ioptions()->optimize_filters_for_hits,
|
||||
cfd->internal_stats(), 1 /* max_threads */,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
false /* is_initial_load */,
|
||||
mutable_cf_options_ptrs[i]->prefix_extractor.get());
|
||||
@@ -3872,15 +3907,19 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
++idx;
|
||||
#endif /* !NDEBUG */
|
||||
s = descriptor_log_->AddRecord(record);
|
||||
if (!s.ok()) {
|
||||
io_s = descriptor_log_->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = SyncManifest(env_, db_options_, descriptor_log_->file());
|
||||
io_s = SyncManifest(env_, db_options_, descriptor_log_->file());
|
||||
}
|
||||
if (!s.ok()) {
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
ROCKS_LOG_ERROR(db_options_->info_log, "MANIFEST write %s\n",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
@@ -3889,8 +3928,12 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// If we just created a new descriptor file, install it by writing a
|
||||
// new CURRENT file that points to it.
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
s = SetCurrentFile(env_, dbname_, pending_manifest_file_number_,
|
||||
db_directory);
|
||||
io_s = SetCurrentFile(fs_, dbname_, pending_manifest_file_number_,
|
||||
db_directory);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
s = io_s;
|
||||
}
|
||||
TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:AfterNewManifest");
|
||||
}
|
||||
|
||||
@@ -4026,7 +4069,7 @@ Status VersionSet::LogAndApply(
|
||||
const autovector<ColumnFamilyData*>& column_family_datas,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<autovector<VersionEdit*>>& edit_lists,
|
||||
InstrumentedMutex* mu, Directory* db_directory, bool new_descriptor_log,
|
||||
InstrumentedMutex* mu, FSDirectory* db_directory, bool new_descriptor_log,
|
||||
const ColumnFamilyOptions* new_cf_options) {
|
||||
mu->AssertHeld();
|
||||
int num_edits = 0;
|
||||
@@ -4452,8 +4495,6 @@ Status VersionSet::Recover(
|
||||
reporter.status = &s;
|
||||
log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter,
|
||||
true /* checksum */, 0 /* log_number */);
|
||||
Slice record;
|
||||
std::string scratch;
|
||||
AtomicGroupReadBuffer read_buffer;
|
||||
s = ReadAndRecover(&reader, &read_buffer, cf_name_to_options,
|
||||
column_families_not_found, builders,
|
||||
@@ -4581,6 +4622,159 @@ Status VersionSet::Recover(
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class ManifestPicker {
|
||||
public:
|
||||
explicit ManifestPicker(const std::string& dbname, FileSystem* fs);
|
||||
void SeekToFirstManifest();
|
||||
// REQUIRES Valid() == true
|
||||
std::string GetNextManifest(uint64_t* file_number, std::string* file_name);
|
||||
bool Valid() const { return manifest_file_iter_ != manifest_files_.end(); }
|
||||
const Status& status() const { return status_; }
|
||||
|
||||
private:
|
||||
const std::string& dbname_;
|
||||
FileSystem* const fs_;
|
||||
// MANIFEST file names(s)
|
||||
std::vector<std::string> manifest_files_;
|
||||
std::vector<std::string>::const_iterator manifest_file_iter_;
|
||||
Status status_;
|
||||
};
|
||||
|
||||
ManifestPicker::ManifestPicker(const std::string& dbname, FileSystem* fs)
|
||||
: dbname_(dbname), fs_(fs) {}
|
||||
|
||||
void ManifestPicker::SeekToFirstManifest() {
|
||||
assert(fs_ != nullptr);
|
||||
std::vector<std::string> children;
|
||||
Status s = fs_->GetChildren(dbname_, IOOptions(), &children, /*dbg=*/nullptr);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
return;
|
||||
}
|
||||
for (const auto& fname : children) {
|
||||
uint64_t file_num = 0;
|
||||
FileType file_type;
|
||||
bool parse_ok = ParseFileName(fname, &file_num, &file_type);
|
||||
if (parse_ok && file_type == kDescriptorFile) {
|
||||
manifest_files_.push_back(fname);
|
||||
}
|
||||
}
|
||||
std::sort(manifest_files_.begin(), manifest_files_.end(),
|
||||
[](const std::string& lhs, const std::string& rhs) {
|
||||
uint64_t num1 = 0;
|
||||
uint64_t num2 = 0;
|
||||
FileType type1;
|
||||
FileType type2;
|
||||
bool parse_ok1 = ParseFileName(lhs, &num1, &type1);
|
||||
bool parse_ok2 = ParseFileName(rhs, &num2, &type2);
|
||||
#ifndef NDEBUG
|
||||
assert(parse_ok1);
|
||||
assert(parse_ok2);
|
||||
#else
|
||||
(void)parse_ok1;
|
||||
(void)parse_ok2;
|
||||
#endif
|
||||
return num1 > num2;
|
||||
});
|
||||
manifest_file_iter_ = manifest_files_.begin();
|
||||
}
|
||||
|
||||
std::string ManifestPicker::GetNextManifest(uint64_t* number,
|
||||
std::string* file_name) {
|
||||
assert(status_.ok());
|
||||
assert(Valid());
|
||||
std::string ret;
|
||||
if (manifest_file_iter_ != manifest_files_.end()) {
|
||||
ret.assign(dbname_);
|
||||
if (ret.back() != kFilePathSeparator) {
|
||||
ret.push_back(kFilePathSeparator);
|
||||
}
|
||||
ret.append(*manifest_file_iter_);
|
||||
if (number) {
|
||||
FileType type;
|
||||
bool parse = ParseFileName(*manifest_file_iter_, number, &type);
|
||||
assert(type == kDescriptorFile);
|
||||
#ifndef NDEBUG
|
||||
assert(parse);
|
||||
#else
|
||||
(void)parse;
|
||||
#endif
|
||||
}
|
||||
if (file_name) {
|
||||
*file_name = *manifest_file_iter_;
|
||||
}
|
||||
++manifest_file_iter_;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status VersionSet::TryRecover(
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
std::string* db_id, bool* has_missing_table_file) {
|
||||
ManifestPicker manifest_picker(dbname_, fs_);
|
||||
manifest_picker.SeekToFirstManifest();
|
||||
Status s = manifest_picker.status();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (!manifest_picker.Valid()) {
|
||||
return Status::Corruption("Cannot locate MANIFEST file in " + dbname_);
|
||||
}
|
||||
std::string manifest_path =
|
||||
manifest_picker.GetNextManifest(&manifest_file_number_, nullptr);
|
||||
while (!manifest_path.empty()) {
|
||||
s = TryRecoverFromOneManifest(manifest_path, column_families, read_only,
|
||||
db_id, has_missing_table_file);
|
||||
if (s.ok() || !manifest_picker.Valid()) {
|
||||
break;
|
||||
}
|
||||
Reset();
|
||||
manifest_path =
|
||||
manifest_picker.GetNextManifest(&manifest_file_number_, nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionSet::TryRecoverFromOneManifest(
|
||||
const std::string& manifest_path,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only,
|
||||
std::string* db_id, bool* has_missing_table_file) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log, "Trying to recover from manifest: %s\n",
|
||||
manifest_path.c_str());
|
||||
std::unique_ptr<SequentialFileReader> manifest_file_reader;
|
||||
Status s;
|
||||
{
|
||||
std::unique_ptr<FSSequentialFile> manifest_file;
|
||||
s = fs_->NewSequentialFile(manifest_path,
|
||||
fs_->OptimizeForManifestRead(file_options_),
|
||||
&manifest_file, nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
manifest_file_reader.reset(
|
||||
new SequentialFileReader(std::move(manifest_file), manifest_path,
|
||||
db_options_->log_readahead_size));
|
||||
}
|
||||
|
||||
VersionSet::LogReporter reporter;
|
||||
reporter.status = &s;
|
||||
log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter,
|
||||
/*checksum=*/true, /*log_num=*/0);
|
||||
{
|
||||
VersionEditHandlerPointInTime handler_pit(read_only, column_families,
|
||||
const_cast<VersionSet*>(this));
|
||||
|
||||
s = handler_pit.Iterate(reader, db_id);
|
||||
|
||||
assert(nullptr != has_missing_table_file);
|
||||
*has_missing_table_file = handler_pit.HasMissingFiles();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
const std::string& dbname,
|
||||
FileSystem* fs) {
|
||||
@@ -4771,9 +4965,10 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
Status s;
|
||||
{
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
s = options.file_system->NewSequentialFile(
|
||||
const std::shared_ptr<FileSystem>& fs = options.env->GetFileSystem();
|
||||
s = fs->NewSequentialFile(
|
||||
dscname,
|
||||
options.file_system->OptimizeForManifestRead(file_options_), &file,
|
||||
fs->OptimizeForManifestRead(file_options_), &file,
|
||||
nullptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -5008,9 +5203,10 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
return Status::Corruption("Unable to Encode VersionEdit:" +
|
||||
edit_for_db_id.DebugString(true));
|
||||
}
|
||||
Status add_record = log->AddRecord(db_id_record);
|
||||
if (!add_record.ok()) {
|
||||
return add_record;
|
||||
IOStatus io_s = log->AddRecord(db_id_record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5035,9 +5231,10 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
return Status::Corruption(
|
||||
"Unable to Encode VersionEdit:" + edit.DebugString(true));
|
||||
}
|
||||
Status s = log->AddRecord(record);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
IOStatus io_s = log->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5066,9 +5263,10 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
return Status::Corruption(
|
||||
"Unable to Encode VersionEdit:" + edit.DebugString(true));
|
||||
}
|
||||
Status s = log->AddRecord(record);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
IOStatus io_s = log->AddRecord(record);
|
||||
if (!io_s.ok()) {
|
||||
io_status_ = io_s;
|
||||
return std::move(io_s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5506,7 +5704,7 @@ void VersionSet::GetObsoleteFiles(std::vector<ObsoleteFileInfo>* files,
|
||||
}
|
||||
|
||||
ColumnFamilyData* VersionSet::CreateColumnFamily(
|
||||
const ColumnFamilyOptions& cf_options, VersionEdit* edit) {
|
||||
const ColumnFamilyOptions& cf_options, const VersionEdit* edit) {
|
||||
assert(edit->is_column_family_add_);
|
||||
|
||||
MutableCFOptions dummy_cf_options;
|
||||
@@ -5561,6 +5759,18 @@ uint64_t VersionSet::GetTotalSstFilesSize(Version* dummy_versions) {
|
||||
return total_files_size;
|
||||
}
|
||||
|
||||
Status VersionSet::VerifyFileMetadata(const std::string& fpath,
|
||||
const FileMetaData& meta) const {
|
||||
uint64_t fsize = 0;
|
||||
Status status = fs_->GetFileSize(fpath, IOOptions(), &fsize, nullptr);
|
||||
if (status.ok()) {
|
||||
if (fsize != meta.fd.GetFileSize()) {
|
||||
status = Status::Corruption("File size mismatch: " + fpath);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
ReactiveVersionSet::ReactiveVersionSet(const std::string& dbname,
|
||||
const ImmutableDBOptions* _db_options,
|
||||
const FileOptions& _file_options,
|
||||
|
||||
+52
-17
@@ -11,8 +11,9 @@
|
||||
// newest version is called "current". Older versions may be kept
|
||||
// around to provide a consistent view to live iterators.
|
||||
//
|
||||
// Each Version keeps track of a set of Table files per level. The
|
||||
// entire set of versions is maintained in a VersionSet.
|
||||
// Each Version keeps track of a set of table files per level, as well as a
|
||||
// set of blob files. The entire set of versions is maintained in a
|
||||
// VersionSet.
|
||||
//
|
||||
// Version,VersionSet are thread-compatible, but require external
|
||||
// synchronization on all accesses.
|
||||
@@ -28,6 +29,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_meta.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction.h"
|
||||
#include "db/compaction/compaction_picker.h"
|
||||
@@ -102,7 +104,7 @@ extern void DoGenerateLevelFilesBrief(LevelFilesBrief* file_level,
|
||||
|
||||
// Information of the storage associated with each Version, including number of
|
||||
// levels of LSM tree, files information at each level, files marked for
|
||||
// compaction, etc.
|
||||
// compaction, blob files, etc.
|
||||
class VersionStorageInfo {
|
||||
public:
|
||||
VersionStorageInfo(const InternalKeyComparator* internal_comparator,
|
||||
@@ -119,6 +121,8 @@ class VersionStorageInfo {
|
||||
|
||||
void AddFile(int level, FileMetaData* f, Logger* info_log = nullptr);
|
||||
|
||||
void AddBlobFile(std::shared_ptr<BlobFileMetaData> blob_file_meta);
|
||||
|
||||
void SetFinalized();
|
||||
|
||||
// Update num_non_empty_levels_.
|
||||
@@ -279,6 +283,10 @@ class VersionStorageInfo {
|
||||
return files_[level];
|
||||
}
|
||||
|
||||
// REQUIRES: This version has been saved (see VersionSet::SaveTo)
|
||||
using BlobFiles = std::map<uint64_t, std::shared_ptr<BlobFileMetaData>>;
|
||||
const BlobFiles& GetBlobFiles() const { return blob_files_; }
|
||||
|
||||
const ROCKSDB_NAMESPACE::LevelFilesBrief& LevelFilesBrief(int level) const {
|
||||
assert(level < static_cast<int>(level_files_brief_.size()));
|
||||
return level_files_brief_[level];
|
||||
@@ -453,6 +461,9 @@ class VersionStorageInfo {
|
||||
// in increasing order of keys
|
||||
std::vector<FileMetaData*>* files_;
|
||||
|
||||
// Map of blob files in version by number.
|
||||
BlobFiles blob_files_;
|
||||
|
||||
// Level that L0 data should be compacted to. All levels < base_level_ should
|
||||
// be empty. -1 if it is not level-compaction so it's not applicable.
|
||||
int base_level_;
|
||||
@@ -553,8 +564,8 @@ class VersionStorageInfo {
|
||||
};
|
||||
|
||||
using MultiGetRange = MultiGetContext::Range;
|
||||
// A column family's version consists of the SST files owned by the column
|
||||
// family at a certain point in time.
|
||||
// A column family's version consists of the table and blob files owned by
|
||||
// the column family at a certain point in time.
|
||||
class Version {
|
||||
public:
|
||||
// Append to *iters a sequence of iterators that will
|
||||
@@ -595,7 +606,7 @@ class Version {
|
||||
// merge_context.operands_list and don't merge the operands
|
||||
// REQUIRES: lock is not held
|
||||
void Get(const ReadOptions&, const LookupKey& key, PinnableSlice* value,
|
||||
Status* status, MergeContext* merge_context,
|
||||
std::string* timestamp, Status* status, MergeContext* merge_context,
|
||||
SequenceNumber* max_covering_tombstone_seq,
|
||||
bool* value_found = nullptr, bool* key_exists = nullptr,
|
||||
SequenceNumber* seq = nullptr, ReadCallback* callback = nullptr,
|
||||
@@ -688,6 +699,8 @@ class Version {
|
||||
FileSystem* fs_;
|
||||
friend class ReactiveVersionSet;
|
||||
friend class VersionSet;
|
||||
friend class VersionEditHandler;
|
||||
friend class VersionEditHandlerPointInTime;
|
||||
|
||||
const InternalKeyComparator* internal_comparator() const {
|
||||
return storage_info_.internal_comparator_;
|
||||
@@ -696,10 +709,6 @@ class Version {
|
||||
return storage_info_.user_comparator_;
|
||||
}
|
||||
|
||||
bool PrefixMayMatch(const ReadOptions& read_options,
|
||||
InternalIterator* level_iter,
|
||||
const Slice& internal_prefix) const;
|
||||
|
||||
// Returns true if the filter blocks in the specified level will not be
|
||||
// checked during read operations. In certain cases (trivial move or preload),
|
||||
// the filter block may already be cached, but we still do not access it such
|
||||
@@ -822,7 +831,7 @@ class VersionSet {
|
||||
Status LogAndApply(
|
||||
ColumnFamilyData* column_family_data,
|
||||
const MutableCFOptions& mutable_cf_options, VersionEdit* edit,
|
||||
InstrumentedMutex* mu, Directory* db_directory = nullptr,
|
||||
InstrumentedMutex* mu, FSDirectory* db_directory = nullptr,
|
||||
bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* column_family_options = nullptr) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
@@ -842,7 +851,7 @@ class VersionSet {
|
||||
ColumnFamilyData* column_family_data,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<VersionEdit*>& edit_list, InstrumentedMutex* mu,
|
||||
Directory* db_directory = nullptr, bool new_descriptor_log = false,
|
||||
FSDirectory* db_directory = nullptr, bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* column_family_options = nullptr) {
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
cfds.emplace_back(column_family_data);
|
||||
@@ -861,7 +870,7 @@ class VersionSet {
|
||||
const autovector<ColumnFamilyData*>& cfds,
|
||||
const autovector<const MutableCFOptions*>& mutable_cf_options_list,
|
||||
const autovector<autovector<VersionEdit*>>& edit_lists,
|
||||
InstrumentedMutex* mu, Directory* db_directory = nullptr,
|
||||
InstrumentedMutex* mu, FSDirectory* db_directory = nullptr,
|
||||
bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* new_cf_options = nullptr);
|
||||
|
||||
@@ -876,6 +885,17 @@ class VersionSet {
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, std::string* db_id = nullptr);
|
||||
|
||||
Status TryRecover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, std::string* db_id,
|
||||
bool* has_missing_table_file);
|
||||
|
||||
// Try to recover the version set to the most recent consistent state
|
||||
// recorded in the specified manifest.
|
||||
Status TryRecoverFromOneManifest(
|
||||
const std::string& manifest_path,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only, std::string* db_id, bool* has_missing_table_file);
|
||||
|
||||
// Reads a manifest file and returns a list of column families in
|
||||
// column_families.
|
||||
static Status ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
@@ -1055,10 +1075,18 @@ class VersionSet {
|
||||
|
||||
static uint64_t GetTotalSstFilesSize(Version* dummy_versions);
|
||||
|
||||
// Get the IO Status returned by written Manifest.
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
// Set the IO Status to OK. Called before Manifest write if needed.
|
||||
void SetIOStatusOK() { io_status_ = IOStatus::OK(); }
|
||||
|
||||
protected:
|
||||
struct ManifestWriter;
|
||||
|
||||
friend class Version;
|
||||
friend class VersionEditHandler;
|
||||
friend class VersionEditHandlerPointInTime;
|
||||
friend class DBImpl;
|
||||
friend class DBImplReadOnly;
|
||||
|
||||
@@ -1069,6 +1097,8 @@ class VersionSet {
|
||||
}
|
||||
};
|
||||
|
||||
void Reset();
|
||||
|
||||
// Returns approximated offset of a key in a file for a given version.
|
||||
uint64_t ApproximateOffsetOf(Version* v, const FdWithKeyRange& f,
|
||||
const Slice& key, TableReaderCaller caller);
|
||||
@@ -1091,7 +1121,7 @@ class VersionSet {
|
||||
void AppendVersion(ColumnFamilyData* column_family_data, Version* v);
|
||||
|
||||
ColumnFamilyData* CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
VersionEdit* edit);
|
||||
const VersionEdit* edit);
|
||||
|
||||
Status ReadAndRecover(
|
||||
log::Reader* reader, AtomicGroupReadBuffer* read_buffer,
|
||||
@@ -1115,8 +1145,10 @@ class VersionSet {
|
||||
const VersionEdit& from_edit,
|
||||
VersionEditParams* version_edit_params);
|
||||
|
||||
std::unique_ptr<ColumnFamilySet> column_family_set_;
|
||||
Status VerifyFileMetadata(const std::string& fpath,
|
||||
const FileMetaData& meta) const;
|
||||
|
||||
std::unique_ptr<ColumnFamilySet> column_family_set_;
|
||||
Env* const env_;
|
||||
FileSystem* const fs_;
|
||||
const std::string dbname_;
|
||||
@@ -1167,10 +1199,13 @@ class VersionSet {
|
||||
|
||||
BlockCacheTracer* const block_cache_tracer_;
|
||||
|
||||
// Store the IO status when Manifest is written
|
||||
IOStatus io_status_;
|
||||
|
||||
private:
|
||||
// REQUIRES db mutex at beginning. may release and re-acquire db mutex
|
||||
Status ProcessManifestWrites(std::deque<ManifestWriter>& writers,
|
||||
InstrumentedMutex* mu, Directory* db_directory,
|
||||
InstrumentedMutex* mu, FSDirectory* db_directory,
|
||||
bool new_descriptor_log,
|
||||
const ColumnFamilyOptions* new_cf_options);
|
||||
|
||||
@@ -1237,7 +1272,7 @@ class ReactiveVersionSet : public VersionSet {
|
||||
const autovector<ColumnFamilyData*>& /*cfds*/,
|
||||
const autovector<const MutableCFOptions*>& /*mutable_cf_options_list*/,
|
||||
const autovector<autovector<VersionEdit*>>& /*edit_lists*/,
|
||||
InstrumentedMutex* /*mu*/, Directory* /*db_directory*/,
|
||||
InstrumentedMutex* /*mu*/, FSDirectory* /*db_directory*/,
|
||||
bool /*new_descriptor_log*/,
|
||||
const ColumnFamilyOptions* /*new_cf_option*/) override {
|
||||
return Status::NotSupported("not supported in reactive mode");
|
||||
|
||||
+776
-22
@@ -10,6 +10,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "logging/logging.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "test_util/testharness.h"
|
||||
@@ -611,16 +612,37 @@ class VersionSetTestBase {
|
||||
const static std::string kColumnFamilyName3;
|
||||
int num_initial_edits_;
|
||||
|
||||
VersionSetTestBase()
|
||||
: env_(Env::Default()),
|
||||
fs_(std::make_shared<LegacyFileSystemWrapper>(env_)),
|
||||
dbname_(test::PerThreadDBPath("version_set_test")),
|
||||
db_options_(),
|
||||
explicit VersionSetTestBase(const std::string& name)
|
||||
: mem_env_(nullptr),
|
||||
env_(nullptr),
|
||||
env_guard_(),
|
||||
fs_(),
|
||||
dbname_(test::PerThreadDBPath(name)),
|
||||
options_(),
|
||||
db_options_(options_),
|
||||
cf_options_(options_),
|
||||
immutable_cf_options_(db_options_, cf_options_),
|
||||
mutable_cf_options_(cf_options_),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(std::make_shared<mock::MockTableFactory>()) {
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
Env* base_env = nullptr;
|
||||
if (test_env_uri) {
|
||||
Status s = Env::LoadEnv(test_env_uri, &base_env, &env_guard_);
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
} else {
|
||||
base_env = Env::Default();
|
||||
}
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
if (getenv("MEM_ENV")) {
|
||||
mem_env_ = new MockEnv(base_env);
|
||||
}
|
||||
env_ = mem_env_ ? mem_env_ : base_env;
|
||||
|
||||
fs_ = std::make_shared<LegacyFileSystemWrapper>(env_);
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
|
||||
db_options_.env = env_;
|
||||
@@ -628,23 +650,38 @@ class VersionSetTestBase {
|
||||
versions_.reset(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_,
|
||||
/*block_cache_tracer=*/nullptr)),
|
||||
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_);
|
||||
/*block_cache_tracer=*/nullptr));
|
||||
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_);
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
|
||||
void PrepareManifest(std::vector<ColumnFamilyDescriptor>* column_families,
|
||||
SequenceNumber* last_seqno,
|
||||
std::unique_ptr<log::Writer>* log_writer) {
|
||||
virtual ~VersionSetTestBase() {
|
||||
if (getenv("KEEP_DB")) {
|
||||
fprintf(stdout, "DB is still at %s\n", dbname_.c_str());
|
||||
} else {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
EXPECT_OK(DestroyDB(dbname_, options));
|
||||
}
|
||||
if (mem_env_) {
|
||||
delete mem_env_;
|
||||
mem_env_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void PrepareManifest(
|
||||
std::vector<ColumnFamilyDescriptor>* column_families,
|
||||
SequenceNumber* last_seqno, std::unique_ptr<log::Writer>* log_writer) {
|
||||
assert(column_families != nullptr);
|
||||
assert(last_seqno != nullptr);
|
||||
assert(log_writer != nullptr);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
DBImpl* impl = new DBImpl(DBOptions(), dbname_);
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(DBOptions(), dbname_));
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
@@ -707,7 +744,7 @@ class VersionSetTestBase {
|
||||
PrepareManifest(&column_families, &last_seqno, &log_writer);
|
||||
log_writer.reset();
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
Status s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
@@ -715,12 +752,25 @@ class VersionSetTestBase {
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies());
|
||||
}
|
||||
|
||||
void VerifyManifest(std::string* manifest_path) const {
|
||||
assert(manifest_path != nullptr);
|
||||
uint64_t manifest_file_number = 0;
|
||||
Status s = versions_->GetCurrentManifestPath(
|
||||
dbname_, fs_.get(), manifest_path, &manifest_file_number);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_EQ(1, manifest_file_number);
|
||||
}
|
||||
|
||||
MockEnv* mem_env_;
|
||||
Env* env_;
|
||||
std::shared_ptr<Env> env_guard_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
const std::string dbname_;
|
||||
EnvOptions env_options_;
|
||||
Options options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
ImmutableCFOptions immutable_cf_options_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
WriteController write_controller_;
|
||||
@@ -738,7 +788,7 @@ const std::string VersionSetTestBase::kColumnFamilyName3 = "charles";
|
||||
|
||||
class VersionSetTest : public VersionSetTestBase, public testing::Test {
|
||||
public:
|
||||
VersionSetTest() : VersionSetTestBase() {}
|
||||
VersionSetTest() : VersionSetTestBase("version_set_test") {}
|
||||
};
|
||||
|
||||
TEST_F(VersionSetTest, SameColumnFamilyGroupCommit) {
|
||||
@@ -780,7 +830,8 @@ TEST_F(VersionSetTest, SameColumnFamilyGroupCommit) {
|
||||
class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
public testing::Test {
|
||||
public:
|
||||
VersionSetAtomicGroupTest() : VersionSetTestBase() {}
|
||||
VersionSetAtomicGroupTest()
|
||||
: VersionSetTestBase("version_set_atomic_group_test") {}
|
||||
|
||||
void SetUp() override {
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
@@ -796,7 +847,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(env_, dbname_, 1, nullptr));
|
||||
ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr));
|
||||
}
|
||||
|
||||
void SetupIncompleteTrailingAtomicGroup(int atomic_group_size) {
|
||||
@@ -808,7 +859,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
edits_[i].MarkAtomicGroup(--remaining);
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(env_, dbname_, 1, nullptr));
|
||||
ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr));
|
||||
}
|
||||
|
||||
void SetupCorruptedAtomicGroup(int atomic_group_size) {
|
||||
@@ -822,7 +873,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(env_, dbname_, 1, nullptr));
|
||||
ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr));
|
||||
}
|
||||
|
||||
void SetupIncorrectAtomicGroup(int atomic_group_size) {
|
||||
@@ -838,7 +889,7 @@ class VersionSetAtomicGroupTest : public VersionSetTestBase,
|
||||
}
|
||||
edits_[i].SetLastSequence(last_seqno_++);
|
||||
}
|
||||
ASSERT_OK(SetCurrentFile(env_, dbname_, 1, nullptr));
|
||||
ASSERT_OK(SetCurrentFile(fs_.get(), dbname_, 1, nullptr));
|
||||
}
|
||||
|
||||
void SetupTestSyncPoints() {
|
||||
@@ -1164,7 +1215,8 @@ TEST_F(VersionSetAtomicGroupTest,
|
||||
class VersionSetTestDropOneCF : public VersionSetTestBase,
|
||||
public testing::TestWithParam<std::string> {
|
||||
public:
|
||||
VersionSetTestDropOneCF() : VersionSetTestBase() {}
|
||||
VersionSetTestDropOneCF()
|
||||
: VersionSetTestBase("version_set_test_drop_one_cf") {}
|
||||
};
|
||||
|
||||
// This test simulates the following execution sequence
|
||||
@@ -1189,7 +1241,7 @@ TEST_P(VersionSetTestDropOneCF, HandleDroppedColumnFamilyInAtomicGroup) {
|
||||
SequenceNumber last_seqno;
|
||||
std::unique_ptr<log::Writer> log_writer;
|
||||
PrepareManifest(&column_families, &last_seqno, &log_writer);
|
||||
Status s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false /* read_only */));
|
||||
@@ -1279,6 +1331,708 @@ INSTANTIATE_TEST_CASE_P(
|
||||
testing::Values(VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3));
|
||||
|
||||
class EmptyDefaultCfNewManifest : public VersionSetTestBase,
|
||||
public testing::Test {
|
||||
public:
|
||||
EmptyDefaultCfNewManifest() : VersionSetTestBase("version_set_new_db_test") {}
|
||||
// Emulate DBImpl::NewDB()
|
||||
void PrepareManifest(std::vector<ColumnFamilyDescriptor>* /*column_families*/,
|
||||
SequenceNumber* /*last_seqno*/,
|
||||
std::unique_ptr<log::Writer>* log_writer) override {
|
||||
assert(log_writer != nullptr);
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
std::unique_ptr<WritableFile> file;
|
||||
const std::string manifest_path = DescriptorFileName(dbname_, 1);
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest_path, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(file)),
|
||||
manifest_path, env_options_));
|
||||
log_writer->reset(new log::Writer(std::move(file_writer), 0, true));
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_db.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
// Create new column family
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(VersionSetTestBase::kColumnFamilyName1);
|
||||
new_cf.SetColumnFamily(1);
|
||||
new_cf.SetLastSequence(2);
|
||||
new_cf.SetNextFile(2);
|
||||
record.clear();
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool write_dbid_to_manifest_ = false;
|
||||
std::unique_ptr<log::Writer> log_writer_;
|
||||
};
|
||||
|
||||
// Create db, create column family. Cf creation will switch to a new MANIFEST.
|
||||
// Then reopen db, trying to recover.
|
||||
TEST_F(EmptyDefaultCfNewManifest, Recover) {
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s =
|
||||
SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options_);
|
||||
column_families.emplace_back(VersionSetTestBase::kColumnFamilyName1,
|
||||
cf_options_);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(
|
||||
manifest_path, column_families, false, &db_id, &has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
}
|
||||
|
||||
class VersionSetTestEmptyDb
|
||||
: public VersionSetTestBase,
|
||||
public testing::TestWithParam<
|
||||
std::tuple<bool, bool, std::vector<std::string>>> {
|
||||
public:
|
||||
static const std::string kUnknownColumnFamilyName;
|
||||
VersionSetTestEmptyDb() : VersionSetTestBase("version_set_test_empty_db") {}
|
||||
|
||||
protected:
|
||||
void PrepareManifest(std::vector<ColumnFamilyDescriptor>* /*column_families*/,
|
||||
SequenceNumber* /*last_seqno*/,
|
||||
std::unique_ptr<log::Writer>* log_writer) override {
|
||||
assert(nullptr != log_writer);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(DBOptions(), dbname_));
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
}
|
||||
const std::string manifest_path = DescriptorFileName(dbname_, 1);
|
||||
std::unique_ptr<WritableFile> file;
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest_path, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(file)),
|
||||
manifest_path, env_options_));
|
||||
{
|
||||
log_writer->reset(new log::Writer(std::move(file_writer), 0, false));
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<log::Writer> log_writer_;
|
||||
};
|
||||
|
||||
const std::string VersionSetTestEmptyDb::kUnknownColumnFamilyName = "unknown";
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
Status s =
|
||||
SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string> cf_names = std::get<2>(GetParam());
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Only a subset of column families in the MANIFEST.
|
||||
VersionEdit new_cf1;
|
||||
new_cf1.AddColumnFamily(VersionSetTestBase::kColumnFamilyName1);
|
||||
new_cf1.SetColumnFamily(1);
|
||||
Status s;
|
||||
{
|
||||
std::string record;
|
||||
new_cf1.EncodeTo(&record);
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string>& cf_names = std::get<2>(GetParam());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
const std::vector<std::string> all_cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
uint32_t cf_id = 1;
|
||||
Status s;
|
||||
for (size_t i = 1; i != all_cf_names.size(); ++i) {
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(all_cf_names[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string>& cf_names = std::get<2>(GetParam());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
const std::vector<std::string> all_cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
uint32_t cf_id = 1;
|
||||
Status s;
|
||||
for (size_t i = 1; i != all_cf_names.size(); ++i) {
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(all_cf_names[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
VersionEdit tmp_edit;
|
||||
tmp_edit.SetColumnFamily(4);
|
||||
tmp_edit.SetLogNumber(0);
|
||||
tmp_edit.SetNextFile(2);
|
||||
tmp_edit.SetLastSequence(0);
|
||||
std::string record;
|
||||
ASSERT_TRUE(tmp_edit.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string>& cf_names = std::get<2>(GetParam());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
const std::vector<std::string> all_cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
uint32_t cf_id = 1;
|
||||
Status s;
|
||||
for (size_t i = 1; i != all_cf_names.size(); ++i) {
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(all_cf_names[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
{
|
||||
VersionEdit tmp_edit;
|
||||
tmp_edit.SetLogNumber(0);
|
||||
tmp_edit.SetNextFile(2);
|
||||
tmp_edit.SetLastSequence(0);
|
||||
std::string record;
|
||||
ASSERT_TRUE(tmp_edit.EncodeTo(&record));
|
||||
s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
log_writer_.reset();
|
||||
s = SetCurrentFile(fs_.get(), dbname_, 1, /*directory_to_fsync=*/nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
|
||||
bool read_only = std::get<1>(GetParam());
|
||||
const std::vector<std::string>& cf_names = std::get<2>(GetParam());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families,
|
||||
read_only, &db_id,
|
||||
&has_missing_table_file);
|
||||
auto iter =
|
||||
std::find(cf_names.begin(), cf_names.end(), kDefaultColumnFamilyName);
|
||||
if (iter == cf_names.end()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else if (read_only) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
} else if (cf_names.size() == all_cf_names.size()) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
} else if (cf_names.size() < all_cf_names.size()) {
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
ColumnFamilyData* cfd = versions_->GetColumnFamilySet()->GetColumnFamily(
|
||||
kUnknownColumnFamilyName);
|
||||
ASSERT_EQ(nullptr, cfd);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
BestEffortRecovery, VersionSetTestEmptyDb,
|
||||
testing::Combine(
|
||||
/*write_dbid_to_manifest=*/testing::Bool(),
|
||||
/*read_only=*/testing::Bool(),
|
||||
/*cf_names=*/
|
||||
testing::Values(
|
||||
std::vector<std::string>(),
|
||||
std::vector<std::string>({kDefaultColumnFamilyName}),
|
||||
std::vector<std::string>({VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3}),
|
||||
std::vector<std::string>({kDefaultColumnFamilyName,
|
||||
VersionSetTestBase::kColumnFamilyName1}),
|
||||
std::vector<std::string>({kDefaultColumnFamilyName,
|
||||
VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3}),
|
||||
std::vector<std::string>(
|
||||
{kDefaultColumnFamilyName,
|
||||
VersionSetTestBase::kColumnFamilyName1,
|
||||
VersionSetTestBase::kColumnFamilyName2,
|
||||
VersionSetTestBase::kColumnFamilyName3,
|
||||
VersionSetTestEmptyDb::kUnknownColumnFamilyName}))));
|
||||
|
||||
class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
public testing::Test {
|
||||
public:
|
||||
VersionSetTestMissingFiles()
|
||||
: VersionSetTestBase("version_set_test_missing_files"),
|
||||
block_based_table_options_(),
|
||||
table_factory_(std::make_shared<BlockBasedTableFactory>(
|
||||
block_based_table_options_)),
|
||||
internal_comparator_(
|
||||
std::make_shared<InternalKeyComparator>(options_.comparator)) {}
|
||||
|
||||
protected:
|
||||
void PrepareManifest(std::vector<ColumnFamilyDescriptor>* column_families,
|
||||
SequenceNumber* last_seqno,
|
||||
std::unique_ptr<log::Writer>* log_writer) override {
|
||||
assert(column_families != nullptr);
|
||||
assert(last_seqno != nullptr);
|
||||
assert(log_writer != nullptr);
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
std::unique_ptr<WritableFile> file;
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
NewLegacyWritableFileWrapper(std::move(file)), manifest, env_options_));
|
||||
log_writer->reset(new log::Writer(std::move(file_writer), 0, false));
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(DBOptions(), dbname_));
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
}
|
||||
{
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_db.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
const std::vector<std::string> cf_names = {
|
||||
kDefaultColumnFamilyName, kColumnFamilyName1, kColumnFamilyName2,
|
||||
kColumnFamilyName3};
|
||||
uint32_t cf_id = 1; // default cf id is 0
|
||||
cf_options_.table_factory = table_factory_;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families->emplace_back(cf_name, cf_options_);
|
||||
if (cf_name == kDefaultColumnFamilyName) {
|
||||
continue;
|
||||
}
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(cf_name);
|
||||
new_cf.SetColumnFamily(cf_id);
|
||||
std::string record;
|
||||
ASSERT_TRUE(new_cf.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
|
||||
VersionEdit cf_files;
|
||||
cf_files.SetColumnFamily(cf_id);
|
||||
cf_files.SetLogNumber(0);
|
||||
record.clear();
|
||||
ASSERT_TRUE(cf_files.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
++cf_id;
|
||||
}
|
||||
SequenceNumber seq = 2;
|
||||
{
|
||||
VersionEdit edit;
|
||||
edit.SetNextFile(7);
|
||||
edit.SetLastSequence(seq);
|
||||
std::string record;
|
||||
ASSERT_TRUE(edit.EncodeTo(&record));
|
||||
s = (*log_writer)->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
*last_seqno = seq + 1;
|
||||
}
|
||||
|
||||
struct SstInfo {
|
||||
uint64_t file_number;
|
||||
std::string column_family;
|
||||
std::string key; // the only key
|
||||
int level = 0;
|
||||
SstInfo(uint64_t file_num, const std::string& cf_name,
|
||||
const std::string& _key)
|
||||
: SstInfo(file_num, cf_name, _key, 0) {}
|
||||
SstInfo(uint64_t file_num, const std::string& cf_name,
|
||||
const std::string& _key, int lvl)
|
||||
: file_number(file_num),
|
||||
column_family(cf_name),
|
||||
key(_key),
|
||||
level(lvl) {}
|
||||
};
|
||||
|
||||
// Create dummy sst, return their metadata. Note that only file name and size
|
||||
// are used.
|
||||
void CreateDummyTableFiles(const std::vector<SstInfo>& file_infos,
|
||||
std::vector<FileMetaData>* file_metas) {
|
||||
assert(file_metas != nullptr);
|
||||
for (const auto& info : file_infos) {
|
||||
uint64_t file_num = info.file_number;
|
||||
std::string fname = MakeTableFileName(dbname_, file_num);
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
Status s = fs_->NewWritableFile(fname, FileOptions(), &file, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> fwriter(
|
||||
new WritableFileWriter(std::move(file), fname, FileOptions(), env_));
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
|
||||
int_tbl_prop_collector_factories;
|
||||
|
||||
std::unique_ptr<TableBuilder> builder(table_factory_->NewTableBuilder(
|
||||
TableBuilderOptions(
|
||||
immutable_cf_options_, mutable_cf_options_, *internal_comparator_,
|
||||
&int_tbl_prop_collector_factories, kNoCompression,
|
||||
/*_sample_for_compression=*/0, CompressionOptions(),
|
||||
/*_skip_filters=*/false, info.column_family, info.level),
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily,
|
||||
fwriter.get()));
|
||||
InternalKey ikey(info.key, 0, ValueType::kTypeValue);
|
||||
builder->Add(ikey.Encode(), "value");
|
||||
ASSERT_OK(builder->Finish());
|
||||
fwriter->Flush();
|
||||
uint64_t file_size = 0;
|
||||
s = fs_->GetFileSize(fname, IOOptions(), &file_size, nullptr);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(0, file_size);
|
||||
FileMetaData meta;
|
||||
meta = FileMetaData(file_num, /*file_path_id=*/0, file_size, ikey, ikey,
|
||||
0, 0, false, 0, 0, 0, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName);
|
||||
file_metas->emplace_back(meta);
|
||||
}
|
||||
}
|
||||
|
||||
// This method updates last_sequence_.
|
||||
void WriteFileAdditionAndDeletionToManifest(
|
||||
uint32_t cf, const std::vector<std::pair<int, FileMetaData>>& added_files,
|
||||
const std::vector<std::pair<int, uint64_t>>& deleted_files) {
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cf);
|
||||
for (const auto& elem : added_files) {
|
||||
int level = elem.first;
|
||||
edit.AddFile(level, elem.second);
|
||||
}
|
||||
for (const auto& elem : deleted_files) {
|
||||
int level = elem.first;
|
||||
edit.DeleteFile(level, elem.second);
|
||||
}
|
||||
edit.SetLastSequence(last_seqno_);
|
||||
++last_seqno_;
|
||||
assert(log_writer_.get() != nullptr);
|
||||
std::string record;
|
||||
ASSERT_TRUE(edit.EncodeTo(&record));
|
||||
Status s = log_writer_->AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
BlockBasedTableOptions block_based_table_options_;
|
||||
std::shared_ptr<TableFactory> table_factory_;
|
||||
std::shared_ptr<InternalKeyComparator> internal_comparator_;
|
||||
std::vector<ColumnFamilyDescriptor> column_families_;
|
||||
SequenceNumber last_seqno_;
|
||||
std::unique_ptr<log::Writer> log_writer_;
|
||||
};
|
||||
|
||||
TEST_F(VersionSetTestMissingFiles, ManifestFarBehindSst) {
|
||||
std::vector<SstInfo> existing_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a"),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "b"),
|
||||
SstInfo(103, kDefaultColumnFamilyName, "c"),
|
||||
SstInfo(107, kDefaultColumnFamilyName, "d"),
|
||||
SstInfo(110, kDefaultColumnFamilyName, "e")};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(existing_files, &file_metas);
|
||||
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (uint64_t file_num = 10; file_num < 15; ++file_num) {
|
||||
std::string smallest_ukey = "a";
|
||||
std::string largest_ukey = "b";
|
||||
InternalKey smallest_ikey(smallest_ukey, 1, ValueType::kTypeValue);
|
||||
InternalKey largest_ikey(largest_ukey, 1, ValueType::kTypeValue);
|
||||
FileMetaData meta =
|
||||
FileMetaData(file_num, /*file_path_id=*/0, /*file_size=*/12,
|
||||
smallest_ikey, largest_ikey, 0, 0, false, 0, 0, 0,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
added_files.emplace_back(0, meta);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
std::vector<std::pair<int, uint64_t>> deleted_files;
|
||||
deleted_files.emplace_back(0, 10);
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
const std::vector<FileMetaData*>& files = vstorage->LevelFiles(0);
|
||||
ASSERT_TRUE(files.empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VersionSetTestMissingFiles, ManifestAheadofSst) {
|
||||
std::vector<SstInfo> existing_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a"),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "b"),
|
||||
SstInfo(103, kDefaultColumnFamilyName, "c"),
|
||||
SstInfo(107, kDefaultColumnFamilyName, "d"),
|
||||
SstInfo(110, kDefaultColumnFamilyName, "e")};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(existing_files, &file_metas);
|
||||
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (size_t i = 3; i != 5; ++i) {
|
||||
added_files.emplace_back(0, file_metas[i]);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
|
||||
added_files.clear();
|
||||
for (uint64_t file_num = 120; file_num < 130; ++file_num) {
|
||||
std::string smallest_ukey = "a";
|
||||
std::string largest_ukey = "b";
|
||||
InternalKey smallest_ikey(smallest_ukey, 1, ValueType::kTypeValue);
|
||||
InternalKey largest_ikey(largest_ukey, 1, ValueType::kTypeValue);
|
||||
FileMetaData meta =
|
||||
FileMetaData(file_num, /*file_path_id=*/0, /*file_size=*/12,
|
||||
smallest_ikey, largest_ikey, 0, 0, false, 0, 0, 0,
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
added_files.emplace_back(0, meta);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
const std::vector<FileMetaData*>& files = vstorage->LevelFiles(0);
|
||||
if (cfd->GetName() == kDefaultColumnFamilyName) {
|
||||
ASSERT_EQ(2, files.size());
|
||||
for (const auto* fmeta : files) {
|
||||
if (fmeta->fd.GetNumber() != 107 && fmeta->fd.GetNumber() != 110) {
|
||||
ASSERT_FALSE(true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ASSERT_TRUE(files.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(VersionSetTestMissingFiles, NoFileMissing) {
|
||||
std::vector<SstInfo> existing_files = {
|
||||
SstInfo(100, kDefaultColumnFamilyName, "a"),
|
||||
SstInfo(102, kDefaultColumnFamilyName, "b"),
|
||||
SstInfo(103, kDefaultColumnFamilyName, "c"),
|
||||
SstInfo(107, kDefaultColumnFamilyName, "d"),
|
||||
SstInfo(110, kDefaultColumnFamilyName, "e")};
|
||||
std::vector<FileMetaData> file_metas;
|
||||
CreateDummyTableFiles(existing_files, &file_metas);
|
||||
|
||||
PrepareManifest(&column_families_, &last_seqno_, &log_writer_);
|
||||
std::vector<std::pair<int, FileMetaData>> added_files;
|
||||
for (const auto& meta : file_metas) {
|
||||
added_files.emplace_back(0, meta);
|
||||
}
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, added_files, std::vector<std::pair<int, uint64_t>>());
|
||||
std::vector<std::pair<int, uint64_t>> deleted_files;
|
||||
deleted_files.emplace_back(/*level=*/0, 100);
|
||||
WriteFileAdditionAndDeletionToManifest(
|
||||
/*cf=*/0, std::vector<std::pair<int, FileMetaData>>(), deleted_files);
|
||||
log_writer_.reset();
|
||||
Status s = SetCurrentFile(fs_.get(), dbname_, 1, nullptr);
|
||||
ASSERT_OK(s);
|
||||
std::string manifest_path;
|
||||
VerifyManifest(&manifest_path);
|
||||
std::string db_id;
|
||||
bool has_missing_table_file = false;
|
||||
s = versions_->TryRecoverFromOneManifest(manifest_path, column_families_,
|
||||
/*read_only=*/false, &db_id,
|
||||
&has_missing_table_file);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_FALSE(has_missing_table_file);
|
||||
for (ColumnFamilyData* cfd : *(versions_->GetColumnFamilySet())) {
|
||||
VersionStorageInfo* vstorage = cfd->current()->storage_info();
|
||||
const std::vector<FileMetaData*>& files = vstorage->LevelFiles(0);
|
||||
if (cfd->GetName() == kDefaultColumnFamilyName) {
|
||||
ASSERT_EQ(existing_files.size() - deleted_files.size(), files.size());
|
||||
bool has_deleted_file = false;
|
||||
for (const auto* fmeta : files) {
|
||||
if (fmeta->fd.GetNumber() == 100) {
|
||||
has_deleted_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_FALSE(has_deleted_file);
|
||||
} else {
|
||||
ASSERT_TRUE(files.empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1
-1
@@ -464,7 +464,7 @@ size_t WriteThread::EnterAsBatchGroupLeader(Writer* leader,
|
||||
}
|
||||
|
||||
if (w->callback != nullptr && !w->callback->AllowWriteBatching()) {
|
||||
// dont batch writes that don't want to be batched
|
||||
// don't batch writes that don't want to be batched
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,9 @@ DECLARE_int32(universal_min_merge_width);
|
||||
DECLARE_int32(universal_max_merge_width);
|
||||
DECLARE_int32(universal_max_size_amplification_percent);
|
||||
DECLARE_int32(clear_column_family_one_in);
|
||||
DECLARE_int32(get_live_files_and_wal_files_one_in);
|
||||
DECLARE_int32(get_live_files_one_in);
|
||||
DECLARE_int32(get_sorted_wal_files_one_in);
|
||||
DECLARE_int32(get_current_wal_file_one_in);
|
||||
DECLARE_int32(set_options_one_in);
|
||||
DECLARE_int32(set_in_place_one_in);
|
||||
DECLARE_int64(cache_size);
|
||||
@@ -167,6 +169,8 @@ DECLARE_double(max_bytes_for_level_multiplier);
|
||||
DECLARE_int32(range_deletion_width);
|
||||
DECLARE_uint64(rate_limiter_bytes_per_sec);
|
||||
DECLARE_bool(rate_limit_bg_reads);
|
||||
DECLARE_uint64(sst_file_manager_bytes_per_sec);
|
||||
DECLARE_uint64(sst_file_manager_bytes_per_truncate);
|
||||
DECLARE_bool(use_txn);
|
||||
DECLARE_uint64(txn_write_policy);
|
||||
DECLARE_bool(unordered_write);
|
||||
|
||||
@@ -256,10 +256,21 @@ DEFINE_int32(clear_column_family_one_in, 1000000,
|
||||
"it again. If N == 0, never drop/create column families. "
|
||||
"When test_batches_snapshots is true, this flag has no effect");
|
||||
|
||||
DEFINE_int32(get_live_files_and_wal_files_one_in, 1000000,
|
||||
"With a chance of 1/N, call GetLiveFiles, GetSortedWalFiles "
|
||||
"and GetCurrentWalFile to verify if it returns correctly. If "
|
||||
"N == 0, never call the three interfaces.");
|
||||
DEFINE_int32(get_live_files_one_in, 1000000,
|
||||
"With a chance of 1/N, call GetLiveFiles to verify if it returns "
|
||||
"correctly. If N == 0, do not call the interface.");
|
||||
|
||||
DEFINE_int32(
|
||||
get_sorted_wal_files_one_in, 1000000,
|
||||
"With a chance of 1/N, call GetSortedWalFiles to verify if it returns "
|
||||
"correctly. (Note that this API may legitimately return an error.) If N == "
|
||||
"0, do not call the interface.");
|
||||
|
||||
DEFINE_int32(
|
||||
get_current_wal_file_one_in, 1000000,
|
||||
"With a chance of 1/N, call GetCurrentWalFile to verify if it returns "
|
||||
"correctly. (Note that this API may legitimately return an error.) If N == "
|
||||
"0, do not call the interface.");
|
||||
|
||||
DEFINE_int32(set_options_one_in, 0,
|
||||
"With a chance of 1/N, change some random options");
|
||||
@@ -430,6 +441,14 @@ DEFINE_uint64(rate_limiter_bytes_per_sec, 0, "Set options.rate_limiter value.");
|
||||
DEFINE_bool(rate_limit_bg_reads, false,
|
||||
"Use options.rate_limiter on compaction reads");
|
||||
|
||||
DEFINE_uint64(sst_file_manager_bytes_per_sec, 0,
|
||||
"Set `Options::sst_file_manager` to delete at this rate. By "
|
||||
"default the deletion rate is unbounded.");
|
||||
|
||||
DEFINE_uint64(sst_file_manager_bytes_per_truncate, 0,
|
||||
"Set `Options::sst_file_manager` to delete in chunks of this "
|
||||
"many bytes. By default whole files will be deleted.");
|
||||
|
||||
DEFINE_bool(use_txn, false,
|
||||
"Use TransactionDB. Currently the default write policy is "
|
||||
"TxnDBWritePolicy::WRITE_PREPARED");
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
#include "db_stress_tool/db_stress_driver.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
StressTest::StressTest()
|
||||
@@ -40,6 +41,7 @@ StressTest::StressTest()
|
||||
}
|
||||
|
||||
Options options;
|
||||
options.env = db_stress_env;
|
||||
// Remove files without preserving manfiest files
|
||||
#ifndef ROCKSDB_LITE
|
||||
const Status s = !FLAGS_use_blob_db
|
||||
@@ -591,13 +593,28 @@ void StressTest::OperateDb(ThreadState* thread) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Every 1 in N verify the one of the following: 1) GetLiveFiles
|
||||
// 2) GetSortedWalFiles 3) GetCurrentWalFile. Each time, randomly select
|
||||
// one of them to run the test.
|
||||
if (thread->rand.OneInOpt(FLAGS_get_live_files_and_wal_files_one_in)) {
|
||||
Status status = VerifyGetLiveAndWalFiles(thread);
|
||||
// Verify GetLiveFiles with a 1 in N chance.
|
||||
if (thread->rand.OneInOpt(FLAGS_get_live_files_one_in)) {
|
||||
Status status = VerifyGetLiveFiles();
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(shared, "VerifyGetLiveAndWalFiles status not OK",
|
||||
VerificationAbort(shared, "VerifyGetLiveFiles status not OK", status);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify GetSortedWalFiles with a 1 in N chance.
|
||||
if (thread->rand.OneInOpt(FLAGS_get_sorted_wal_files_one_in)) {
|
||||
Status status = VerifyGetSortedWalFiles();
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(shared, "VerifyGetSortedWalFiles status not OK",
|
||||
status);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify GetCurrentWalFile with a 1 in N chance.
|
||||
if (thread->rand.OneInOpt(FLAGS_get_current_wal_file_one_in)) {
|
||||
Status status = VerifyGetCurrentWalFile();
|
||||
if (!status.ok()) {
|
||||
VerificationAbort(shared, "VerifyGetCurrentWalFile status not OK",
|
||||
status);
|
||||
}
|
||||
}
|
||||
@@ -976,28 +993,23 @@ Status StressTest::TestIterate(ThreadState* thread,
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Test the return status of GetLiveFiles, GetSortedWalFiles, and
|
||||
// GetCurrentWalFile. Each time, randomly select one of them to run
|
||||
// and return the status.
|
||||
Status StressTest::VerifyGetLiveAndWalFiles(ThreadState* thread) {
|
||||
int case_num = thread->rand.Uniform(3);
|
||||
if (case_num == 0) {
|
||||
std::vector<std::string> live_file;
|
||||
uint64_t manifest_size;
|
||||
return db_->GetLiveFiles(live_file, &manifest_size);
|
||||
}
|
||||
// Test the return status of GetLiveFiles.
|
||||
Status StressTest::VerifyGetLiveFiles() const {
|
||||
std::vector<std::string> live_file;
|
||||
uint64_t manifest_size = 0;
|
||||
return db_->GetLiveFiles(live_file, &manifest_size);
|
||||
}
|
||||
|
||||
if (case_num == 1) {
|
||||
VectorLogPtr log_ptr;
|
||||
return db_->GetSortedWalFiles(log_ptr);
|
||||
}
|
||||
// Test the return status of GetSortedWalFiles.
|
||||
Status StressTest::VerifyGetSortedWalFiles() const {
|
||||
VectorLogPtr log_ptr;
|
||||
return db_->GetSortedWalFiles(log_ptr);
|
||||
}
|
||||
|
||||
if (case_num == 2) {
|
||||
std::unique_ptr<LogFile> cur_wal_file;
|
||||
return db_->GetCurrentWalFile(&cur_wal_file);
|
||||
}
|
||||
assert(false);
|
||||
return Status::Corruption("Undefined case happens!");
|
||||
// Test the return status of GetCurrentWalFile.
|
||||
Status StressTest::VerifyGetCurrentWalFile() const {
|
||||
std::unique_ptr<LogFile> cur_wal_file;
|
||||
return db_->GetCurrentWalFile(&cur_wal_file);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -1839,6 +1851,21 @@ void StressTest::Open() {
|
||||
options_.new_table_reader_for_compaction_inputs = true;
|
||||
}
|
||||
}
|
||||
if (FLAGS_sst_file_manager_bytes_per_sec > 0 ||
|
||||
FLAGS_sst_file_manager_bytes_per_truncate > 0) {
|
||||
Status status;
|
||||
options_.sst_file_manager.reset(NewSstFileManager(
|
||||
db_stress_env, options_.info_log, "" /* trash_dir */,
|
||||
static_cast<int64_t>(FLAGS_sst_file_manager_bytes_per_sec),
|
||||
true /* delete_existing_trash */, &status,
|
||||
0.25 /* max_trash_db_ratio */,
|
||||
FLAGS_sst_file_manager_bytes_per_truncate));
|
||||
if (!status.ok()) {
|
||||
fprintf(stderr, "SstFileManager creation failed: %s\n",
|
||||
status.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (FLAGS_prefix_size == 0 && FLAGS_rep_factory == kHashSkipList) {
|
||||
fprintf(stderr,
|
||||
|
||||
@@ -184,7 +184,10 @@ class StressTest {
|
||||
|
||||
Status MaybeReleaseSnapshots(ThreadState* thread, uint64_t i);
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status VerifyGetLiveAndWalFiles(ThreadState* thread);
|
||||
Status VerifyGetLiveFiles() const;
|
||||
Status VerifyGetSortedWalFiles() const;
|
||||
Status VerifyGetCurrentWalFile() const;
|
||||
|
||||
virtual Status TestApproximateSize(
|
||||
ThreadState* thread, uint64_t iteration,
|
||||
const std::vector<int>& rand_column_families,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# defs.bzl - Definitions for Facebook-specific buck build integration
|
||||
# in TARGETS
|
||||
|
||||
load("@fbcode_macros//build_defs:coverage.bzl", "coverage")
|
||||
load("@fbcode_macros//build_defs:cpp_binary.bzl", "cpp_binary")
|
||||
load("@fbcode_macros//build_defs:custom_unittest.bzl", "custom_unittest")
|
||||
|
||||
@@ -35,8 +36,21 @@ def test_binary(
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
|
||||
binary_path = "$(location :{})".format(test_bin)
|
||||
|
||||
base_path = native.package_name()
|
||||
tags = []
|
||||
if coverage.is_coverage_enabled(base_path):
|
||||
# This tag instructs testpilot to use
|
||||
# the lower-memory coverage runner
|
||||
# (e.g. it tells testpilot that the binary
|
||||
# is actually instrumented with coverage info)
|
||||
tags = ["coverage"]
|
||||
|
||||
custom_unittest(
|
||||
name = test_name,
|
||||
command = [TEST_RUNNER, "$(location :{})".format(test_bin)],
|
||||
command = [TEST_RUNNER, binary_path],
|
||||
type = ttype,
|
||||
env = {"BUCK_BASE_BINARY": binary_path},
|
||||
tags = tags,
|
||||
)
|
||||
Vendored
+59
-69
@@ -20,22 +20,6 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// Options::env only, whereas in the latter case, the user will specify
|
||||
// Options::env and Options::file_system.
|
||||
|
||||
inline IOStatus status_to_io_status(Status&& status) {
|
||||
if (status.ok()) {
|
||||
// Fast path
|
||||
return IOStatus::OK();
|
||||
} else {
|
||||
const char* state = status.getState();
|
||||
if (state) {
|
||||
return IOStatus(status.code(), status.subcode(),
|
||||
Slice(state, strlen(status.getState()) + 1),
|
||||
Slice());
|
||||
} else {
|
||||
return IOStatus(status.code(), status.subcode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CompositeSequentialFileWrapper : public SequentialFile {
|
||||
public:
|
||||
explicit CompositeSequentialFileWrapper(
|
||||
@@ -291,14 +275,19 @@ class CompositeEnvWrapper : public Env {
|
||||
public:
|
||||
// Initialize a CompositeEnvWrapper that delegates all thread/time related
|
||||
// calls to env, and all file operations to fs
|
||||
explicit CompositeEnvWrapper(Env* env, FileSystem* fs)
|
||||
: env_target_(env), fs_env_target_(fs) {}
|
||||
explicit CompositeEnvWrapper(Env* env, std::shared_ptr<FileSystem> fs)
|
||||
: Env(fs), env_target_(env) {}
|
||||
~CompositeEnvWrapper() {}
|
||||
|
||||
// Return the target to which this Env forwards all calls
|
||||
Env* env_target() const { return env_target_; }
|
||||
|
||||
FileSystem* fs_env_target() const { return fs_env_target_; }
|
||||
Status RegisterDbPaths(const std::vector<std::string>& paths) override {
|
||||
return file_system_->RegisterDbPaths(paths);
|
||||
}
|
||||
Status UnregisterDbPaths(const std::vector<std::string>& paths) override {
|
||||
return file_system_->UnregisterDbPaths(paths);
|
||||
}
|
||||
|
||||
// The following text is boilerplate that forwards all methods to target()
|
||||
Status NewSequentialFile(const std::string& f,
|
||||
@@ -308,7 +297,7 @@ class CompositeEnvWrapper : public Env {
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
Status status;
|
||||
status =
|
||||
fs_env_target_->NewSequentialFile(f, FileOptions(options), &file, &dbg);
|
||||
file_system_->NewSequentialFile(f, FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
r->reset(new CompositeSequentialFileWrapper(file));
|
||||
}
|
||||
@@ -320,8 +309,8 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
Status status;
|
||||
status = fs_env_target_->NewRandomAccessFile(f, FileOptions(options), &file,
|
||||
&dbg);
|
||||
status =
|
||||
file_system_->NewRandomAccessFile(f, FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
r->reset(new CompositeRandomAccessFileWrapper(file));
|
||||
}
|
||||
@@ -333,7 +322,7 @@ class CompositeEnvWrapper : public Env {
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
Status status;
|
||||
status =
|
||||
fs_env_target_->NewWritableFile(f, FileOptions(options), &file, &dbg);
|
||||
file_system_->NewWritableFile(f, FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
r->reset(new CompositeWritableFileWrapper(file));
|
||||
}
|
||||
@@ -345,8 +334,8 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
Status status;
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
status = fs_env_target_->ReopenWritableFile(fname, FileOptions(options),
|
||||
&file, &dbg);
|
||||
status = file_system_->ReopenWritableFile(fname, FileOptions(options),
|
||||
&file, &dbg);
|
||||
if (status.ok()) {
|
||||
result->reset(new CompositeWritableFileWrapper(file));
|
||||
}
|
||||
@@ -359,8 +348,8 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
Status status;
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
status = fs_env_target_->ReuseWritableFile(
|
||||
fname, old_fname, FileOptions(options), &file, &dbg);
|
||||
status = file_system_->ReuseWritableFile(fname, old_fname,
|
||||
FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
r->reset(new CompositeWritableFileWrapper(file));
|
||||
}
|
||||
@@ -372,8 +361,8 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
std::unique_ptr<FSRandomRWFile> file;
|
||||
Status status;
|
||||
status = fs_env_target_->NewRandomRWFile(fname, FileOptions(options), &file,
|
||||
&dbg);
|
||||
status =
|
||||
file_system_->NewRandomRWFile(fname, FileOptions(options), &file, &dbg);
|
||||
if (status.ok()) {
|
||||
result->reset(new CompositeRandomRWFileWrapper(file));
|
||||
}
|
||||
@@ -382,7 +371,7 @@ class CompositeEnvWrapper : public Env {
|
||||
Status NewMemoryMappedFileBuffer(
|
||||
const std::string& fname,
|
||||
std::unique_ptr<MemoryMappedFileBuffer>* result) override {
|
||||
return fs_env_target_->NewMemoryMappedFileBuffer(fname, result);
|
||||
return file_system_->NewMemoryMappedFileBuffer(fname, result);
|
||||
}
|
||||
Status NewDirectory(const std::string& name,
|
||||
std::unique_ptr<Directory>* result) override {
|
||||
@@ -390,7 +379,7 @@ class CompositeEnvWrapper : public Env {
|
||||
IODebugContext dbg;
|
||||
std::unique_ptr<FSDirectory> dir;
|
||||
Status status;
|
||||
status = fs_env_target_->NewDirectory(name, io_opts, &dir, &dbg);
|
||||
status = file_system_->NewDirectory(name, io_opts, &dir, &dbg);
|
||||
if (status.ok()) {
|
||||
result->reset(new CompositeDirectoryWrapper(dir));
|
||||
}
|
||||
@@ -399,102 +388,108 @@ class CompositeEnvWrapper : public Env {
|
||||
Status FileExists(const std::string& f) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->FileExists(f, io_opts, &dbg);
|
||||
return file_system_->FileExists(f, io_opts, &dbg);
|
||||
}
|
||||
Status GetChildren(const std::string& dir,
|
||||
std::vector<std::string>* r) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetChildren(dir, io_opts, r, &dbg);
|
||||
return file_system_->GetChildren(dir, io_opts, r, &dbg);
|
||||
}
|
||||
Status GetChildrenFileAttributes(
|
||||
const std::string& dir, std::vector<FileAttributes>* result) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetChildrenFileAttributes(dir, io_opts, result,
|
||||
&dbg);
|
||||
return file_system_->GetChildrenFileAttributes(dir, io_opts, result, &dbg);
|
||||
}
|
||||
Status DeleteFile(const std::string& f) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->DeleteFile(f, io_opts, &dbg);
|
||||
return file_system_->DeleteFile(f, io_opts, &dbg);
|
||||
}
|
||||
Status Truncate(const std::string& fname, size_t size) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->Truncate(fname, size, io_opts, &dbg);
|
||||
return file_system_->Truncate(fname, size, io_opts, &dbg);
|
||||
}
|
||||
Status CreateDir(const std::string& d) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->CreateDir(d, io_opts, &dbg);
|
||||
return file_system_->CreateDir(d, io_opts, &dbg);
|
||||
}
|
||||
Status CreateDirIfMissing(const std::string& d) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->CreateDirIfMissing(d, io_opts, &dbg);
|
||||
return file_system_->CreateDirIfMissing(d, io_opts, &dbg);
|
||||
}
|
||||
Status DeleteDir(const std::string& d) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->DeleteDir(d, io_opts, &dbg);
|
||||
return file_system_->DeleteDir(d, io_opts, &dbg);
|
||||
}
|
||||
Status GetFileSize(const std::string& f, uint64_t* s) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetFileSize(f, io_opts, s, &dbg);
|
||||
return file_system_->GetFileSize(f, io_opts, s, &dbg);
|
||||
}
|
||||
|
||||
Status GetFileModificationTime(const std::string& fname,
|
||||
uint64_t* file_mtime) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetFileModificationTime(fname, io_opts, file_mtime,
|
||||
&dbg);
|
||||
return file_system_->GetFileModificationTime(fname, io_opts, file_mtime,
|
||||
&dbg);
|
||||
}
|
||||
|
||||
Status RenameFile(const std::string& s, const std::string& t) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->RenameFile(s, t, io_opts, &dbg);
|
||||
return file_system_->RenameFile(s, t, io_opts, &dbg);
|
||||
}
|
||||
|
||||
Status LinkFile(const std::string& s, const std::string& t) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->LinkFile(s, t, io_opts, &dbg);
|
||||
return file_system_->LinkFile(s, t, io_opts, &dbg);
|
||||
}
|
||||
|
||||
Status NumFileLinks(const std::string& fname, uint64_t* count) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->NumFileLinks(fname, io_opts, count, &dbg);
|
||||
return file_system_->NumFileLinks(fname, io_opts, count, &dbg);
|
||||
}
|
||||
|
||||
Status AreFilesSame(const std::string& first, const std::string& second,
|
||||
bool* res) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->AreFilesSame(first, second, io_opts, res, &dbg);
|
||||
return file_system_->AreFilesSame(first, second, io_opts, res, &dbg);
|
||||
}
|
||||
|
||||
Status LockFile(const std::string& f, FileLock** l) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->LockFile(f, io_opts, l, &dbg);
|
||||
return file_system_->LockFile(f, io_opts, l, &dbg);
|
||||
}
|
||||
|
||||
Status UnlockFile(FileLock* l) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->UnlockFile(l, io_opts, &dbg);
|
||||
return file_system_->UnlockFile(l, io_opts, &dbg);
|
||||
}
|
||||
|
||||
Status GetAbsolutePath(const std::string& db_path,
|
||||
std::string* output_path) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetAbsolutePath(db_path, io_opts, output_path, &dbg);
|
||||
return file_system_->GetAbsolutePath(db_path, io_opts, output_path, &dbg);
|
||||
}
|
||||
|
||||
Status NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return file_system_->NewLogger(fname, io_opts, result, &dbg);
|
||||
}
|
||||
|
||||
#if !defined(OS_WIN) && !defined(ROCKSDB_NO_DYNAMIC_EXTENSION)
|
||||
@@ -524,10 +519,6 @@ class CompositeEnvWrapper : public Env {
|
||||
Status GetTestDirectory(std::string* path) override {
|
||||
return env_target_->GetTestDirectory(path);
|
||||
}
|
||||
Status NewLogger(const std::string& fname,
|
||||
std::shared_ptr<Logger>* result) override {
|
||||
return env_target_->NewLogger(fname, result);
|
||||
}
|
||||
uint64_t NowMicros() override { return env_target_->NowMicros(); }
|
||||
uint64_t NowNanos() override { return env_target_->NowNanos(); }
|
||||
uint64_t NowCPUNanos() override { return env_target_->NowCPUNanos(); }
|
||||
@@ -583,46 +574,41 @@ class CompositeEnvWrapper : public Env {
|
||||
}
|
||||
|
||||
EnvOptions OptimizeForLogRead(const EnvOptions& env_options) const override {
|
||||
return fs_env_target_->OptimizeForLogRead(FileOptions(env_options));
|
||||
return file_system_->OptimizeForLogRead(FileOptions(env_options));
|
||||
}
|
||||
EnvOptions OptimizeForManifestRead(
|
||||
const EnvOptions& env_options) const override {
|
||||
return fs_env_target_->OptimizeForManifestRead(
|
||||
FileOptions(env_options));
|
||||
return file_system_->OptimizeForManifestRead(FileOptions(env_options));
|
||||
}
|
||||
EnvOptions OptimizeForLogWrite(const EnvOptions& env_options,
|
||||
const DBOptions& db_options) const override {
|
||||
return fs_env_target_->OptimizeForLogWrite(FileOptions(env_options),
|
||||
db_options);
|
||||
return file_system_->OptimizeForLogWrite(FileOptions(env_options),
|
||||
db_options);
|
||||
}
|
||||
EnvOptions OptimizeForManifestWrite(
|
||||
const EnvOptions& env_options) const override {
|
||||
return fs_env_target_->OptimizeForManifestWrite(
|
||||
FileOptions(env_options));
|
||||
return file_system_->OptimizeForManifestWrite(FileOptions(env_options));
|
||||
}
|
||||
EnvOptions OptimizeForCompactionTableWrite(
|
||||
const EnvOptions& env_options,
|
||||
const ImmutableDBOptions& immutable_ops) const override {
|
||||
return fs_env_target_->OptimizeForCompactionTableWrite(
|
||||
FileOptions(env_options),
|
||||
immutable_ops);
|
||||
return file_system_->OptimizeForCompactionTableWrite(
|
||||
FileOptions(env_options), immutable_ops);
|
||||
}
|
||||
EnvOptions OptimizeForCompactionTableRead(
|
||||
const EnvOptions& env_options,
|
||||
const ImmutableDBOptions& db_options) const override {
|
||||
return fs_env_target_->OptimizeForCompactionTableRead(
|
||||
FileOptions(env_options),
|
||||
db_options);
|
||||
return file_system_->OptimizeForCompactionTableRead(
|
||||
FileOptions(env_options), db_options);
|
||||
}
|
||||
Status GetFreeSpace(const std::string& path, uint64_t* diskfree) override {
|
||||
IOOptions io_opts;
|
||||
IODebugContext dbg;
|
||||
return fs_env_target_->GetFreeSpace(path, io_opts, diskfree, &dbg);
|
||||
return file_system_->GetFreeSpace(path, io_opts, diskfree, &dbg);
|
||||
}
|
||||
|
||||
private:
|
||||
Env* env_target_;
|
||||
FileSystem* fs_env_target_;
|
||||
};
|
||||
|
||||
class LegacySequentialFileWrapper : public FSSequentialFile {
|
||||
@@ -1060,6 +1046,10 @@ class LegacyFileSystemWrapper : public FileSystem {
|
||||
return status_to_io_status(target_->NewLogger(fname, result));
|
||||
}
|
||||
|
||||
void SanitizeFileOptions(FileOptions* opts) const override {
|
||||
target_->SanitizeEnvOptions(opts);
|
||||
}
|
||||
|
||||
FileOptions OptimizeForLogRead(
|
||||
const FileOptions& file_options) const override {
|
||||
return target_->OptimizeForLogRead(file_options);
|
||||
|
||||
Loaded 100 of 268 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user