mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 14:47:40 +08:00
Compare commits
126 Commits
tests
...
improve-support
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f06b4fa01 | |||
| ef7815b803 | |||
| 91d7c03cdc | |||
| 90fc40690a | |||
| 21171615c1 | |||
| 8581a93a6b | |||
| 23b76252c8 | |||
| 8527012bb6 | |||
| 537a233941 | |||
| e3eba52a5d | |||
| 7bee48bdbd | |||
| de98fd88e3 | |||
| 97fe23fc5c | |||
| 520bbb1774 | |||
| 90ebf1a257 | |||
| d4ad32d7bd | |||
| 63904434eb | |||
| dbeaa0d397 | |||
| 5f3088d565 | |||
| 121e321549 | |||
| a61ff876a1 | |||
| 1ea83c5de9 | |||
| 35b38a232c | |||
| 5cd8240b86 | |||
| cfee7fb51a | |||
| 1c912196de | |||
| 331cb63641 | |||
| 440621aab8 | |||
| 926f3a78a6 | |||
| b9846370e9 | |||
| db7ae0a485 | |||
| d4d9fe8e57 | |||
| 35b83327a7 | |||
| 39218a72a4 | |||
| 17027aeffc | |||
| 0d234dfce4 | |||
| e099c2dd55 | |||
| 36fa49ceb5 | |||
| 2462763b2e | |||
| 29ffbb8a50 | |||
| b3efb1cbe0 | |||
| 8ad63a4b86 | |||
| 25403c2265 | |||
| 52d4c9b7f6 | |||
| 26d67e357e | |||
| 0a5b5d88b2 | |||
| b557499eee | |||
| 7f850b889d | |||
| 14f409c0f1 | |||
| 1f6efabe23 | |||
| 235ab9dd32 | |||
| e8f9d7f0d4 | |||
| 818c84e116 | |||
| e5ae1bb465 | |||
| a16e00b7b9 | |||
| 17339dc2f3 | |||
| 408205a36b | |||
| 967aa8157a | |||
| 2694b6dc26 | |||
| a8e503e545 | |||
| 346d1069c3 | |||
| 6d454d7376 | |||
| 189f0c27aa | |||
| a71e467381 | |||
| 2729dd72ad | |||
| 80ade9ad83 | |||
| c726f7fda8 | |||
| 64c85d0d97 | |||
| 795e663df0 | |||
| 0a5b16c7c5 | |||
| 645e57c22d | |||
| 58c221440c | |||
| 397495964b | |||
| 7103559f49 | |||
| 524c6e6b72 | |||
| 14cee194d6 | |||
| 61d69d450d | |||
| 28a9d8910b | |||
| 92ee3350e0 | |||
| 7f3a634e06 | |||
| 8585059ae0 | |||
| de2c6fb158 | |||
| c766887458 | |||
| 7b4b43febb | |||
| e5bee404ce | |||
| e750dacffb | |||
| 80bc35927c | |||
| c48764ba47 | |||
| 3fbc865cd5 | |||
| 7b5f7ff0b4 | |||
| 906a602c2c | |||
| f23fed19a1 | |||
| 4faaab70a6 | |||
| f5281a53a4 | |||
| fbe3b9e2b6 | |||
| 1f32dc7d2b | |||
| 7497f992e0 | |||
| dd216dd76a | |||
| a720401877 | |||
| d82f1421b4 | |||
| 9d347332fb | |||
| 9c7da963bc | |||
| 3593275357 | |||
| f4502944c3 | |||
| 3470c75852 | |||
| 23e1d23675 | |||
| 0a0860a5fb | |||
| 45b6bcca98 | |||
| b73652169e | |||
| 5504a056f8 | |||
| 4420df4b0e | |||
| f1592a06c2 | |||
| 812c7371d3 | |||
| d0c38c0c8c | |||
| 2210152947 | |||
| ab2254bedf | |||
| f4b72d7056 | |||
| 4f297ad05f | |||
| 50d7ac0ea3 | |||
| fea2b1dfb2 | |||
| 88c3ee2d31 | |||
| 01e3c30def | |||
| 89b37081a1 | |||
| 2a0dfaa044 | |||
| 44cf84932f | |||
| 2807678b11 |
+52
-13
@@ -15,8 +15,17 @@ cache:
|
||||
- apt
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages: ['zlib1g-dev', 'libbz2-dev', 'libsnappy-dev', 'curl', 'libgflags-dev', 'mingw-w64']
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- curl
|
||||
- g++-8
|
||||
- libbz2-dev
|
||||
- libgflags-dev
|
||||
- libsnappy-dev
|
||||
- mingw-w64
|
||||
- zlib1g-dev
|
||||
env:
|
||||
- TEST_GROUP=platform_dependent # 16-18 minutes
|
||||
- TEST_GROUP=1 # 33-35 minutes
|
||||
@@ -30,6 +39,7 @@ env:
|
||||
# Build examples
|
||||
- JOB_NAME=examples # 5-7 minutes
|
||||
- JOB_NAME=cmake # 3-5 minutes
|
||||
- JOB_NAME=cmake-gcc8 # 3-5 minutes
|
||||
- JOB_NAME=cmake-mingw # 3 minutes
|
||||
|
||||
matrix:
|
||||
@@ -42,6 +52,8 @@ matrix:
|
||||
env: TEST_GROUP=3
|
||||
- os: osx
|
||||
env: TEST_GROUP=4
|
||||
- os: osx
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- os : osx
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os : linux
|
||||
@@ -52,9 +64,12 @@ matrix:
|
||||
# https://docs.travis-ci.com/user/caching/#ccache-cache
|
||||
install:
|
||||
- if [ "${TRAVIS_OS_NAME}" == osx ]; then
|
||||
brew install ccache;
|
||||
brew install ccache zstd lz4 snappy xz;
|
||||
PATH=$PATH:/usr/local/opt/ccache/libexec;
|
||||
fi
|
||||
- if [ "${JOB_NAME}" == cmake-gcc8 ]; then
|
||||
CC=gcc-8 && CXX=g++-8;
|
||||
fi
|
||||
- if [[ "${JOB_NAME}" == cmake* ]] && [ "${TRAVIS_OS_NAME}" == linux ]; then
|
||||
mkdir cmake-dist && curl -sfSL https://cmake.org/files/v3.8/cmake-3.8.1-Linux-x86_64.tar.gz | tar --strip-components=1 -C cmake-dist -xz && export PATH=$PWD/cmake-dist/bin:$PATH;
|
||||
fi
|
||||
@@ -67,16 +82,40 @@ before_script:
|
||||
script:
|
||||
- ${CXX} --version
|
||||
- if [ `command -v ccache` ]; then ccache -C; fi
|
||||
- if [ "${TEST_GROUP}" == 'platform_dependent' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_END=db_block_cache_test make -j4 all_but_some_tests check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '1' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=db_block_cache_test ROCKSDBTESTS_END=full_filter_block_test make -j4 check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '2' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=full_filter_block_test ROCKSDBTESTS_END=write_batch_with_index_test make -j4 check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '3' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_batch_with_index_test ROCKSDBTESTS_END=write_prepared_transaction_test make -j4 check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '4' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_prepared_transaction_test make -j4 check_some; fi
|
||||
- if [ "${JOB_NAME}" == 'java_test' ]; then OPT=-DTRAVIS V=1 make clean jclean && make rocksdbjava jtest; fi
|
||||
- if [ "${JOB_NAME}" == 'lite_build' ]; then OPT="-DTRAVIS -DROCKSDB_LITE" V=1 make -j4 static_lib tools; fi
|
||||
- if [ "${JOB_NAME}" == 'examples' ]; then OPT=-DTRAVIS V=1 make -j4 static_lib; cd examples; make -j4; fi
|
||||
- if [ "${JOB_NAME}" == 'cmake' ]; then mkdir build && cd build && cmake -DJNI=1 .. && make -j4 rocksdb rocksdbjni; fi
|
||||
- if [ "${JOB_NAME}" == 'cmake-mingw' ]; then mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni; fi
|
||||
- case $TEST_GROUP in
|
||||
platform_dependent)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_END=db_block_cache_test make -j4 all_but_some_tests check_some
|
||||
;;
|
||||
1)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=db_block_cache_test ROCKSDBTESTS_END=full_filter_block_test make -j4 check_some
|
||||
;;
|
||||
2)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=full_filter_block_test ROCKSDBTESTS_END=write_batch_with_index_test make -j4 check_some
|
||||
;;
|
||||
3)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_batch_with_index_test ROCKSDBTESTS_END=write_prepared_transaction_test make -j4 check_some
|
||||
;;
|
||||
4)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_prepared_transaction_test make -j4 check_some
|
||||
;;
|
||||
esac
|
||||
- case $JOB_NAME in
|
||||
java_test)
|
||||
OPT=-DTRAVIS V=1 make clean jclean && make rocksdbjava jtest
|
||||
;;
|
||||
lite_build)
|
||||
OPT='-DTRAVIS -DROCKSDB_LITE' V=1 make -j4 static_lib tools
|
||||
;;
|
||||
examples)
|
||||
OPT=-DTRAVIS V=1 make -j4 static_lib && cd examples && make -j4
|
||||
;;
|
||||
cmake-mingw)
|
||||
mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni
|
||||
;;
|
||||
cmake*)
|
||||
mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_BUILD_TYPE=Release && make -j4 rocksdb rocksdbjni
|
||||
;;
|
||||
esac
|
||||
notifications:
|
||||
email:
|
||||
- leveldb@fb.com
|
||||
|
||||
+19
-5
@@ -170,7 +170,7 @@ else()
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -fno-omit-frame-pointer")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
|
||||
include(CheckCXXCompilerFlag)
|
||||
CHECK_CXX_COMPILER_FLAG("-momit-leaf-frame-pointer" HAVE_OMIT_LEAF_FRAME_POINTER)
|
||||
if(HAVE_OMIT_LEAF_FRAME_POINTER)
|
||||
@@ -322,14 +322,24 @@ if(DEFINED USE_RTTI)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DROCKSDB_USE_RTTI")
|
||||
else()
|
||||
message(STATUS "Disabling RTTI")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-rtti")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
|
||||
if(MSVC)
|
||||
message(STATUS "Disabling RTTI in Release builds. Always on in Debug.")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
|
||||
else()
|
||||
message(STATUS "Disabling RTTI in Release builds")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-rtti")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "Enabling RTTI in Debug builds only (default)")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
@@ -471,6 +481,7 @@ set(SOURCES
|
||||
db/db_info_dumper.cc
|
||||
db/db_iter.cc
|
||||
db/dbformat.cc
|
||||
db/error_handler.cc
|
||||
db/event_helpers.cc
|
||||
db/experimental.cc
|
||||
db/external_sst_file_ingestion_job.cc
|
||||
@@ -574,6 +585,7 @@ set(SOURCES
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
util/comparator.cc
|
||||
util/compression_context_cache.cc
|
||||
util/concurrent_arena.cc
|
||||
util/crc32c.cc
|
||||
util/delete_scheduler.cc
|
||||
@@ -866,6 +878,7 @@ if(WITH_TESTS)
|
||||
db/db_write_test.cc
|
||||
db/dbformat_test.cc
|
||||
db/deletefile_test.cc
|
||||
db/error_handler_test.cc
|
||||
db/obsolete_files_test.cc
|
||||
db/external_sst_file_basic_test.cc
|
||||
db/external_sst_file_test.cc
|
||||
@@ -959,6 +972,7 @@ if(WITH_TESTS)
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
utilities/transactions/transaction_test.cc
|
||||
utilities/transactions/write_prepared_transaction_test.cc
|
||||
utilities/transactions/write_unprepared_transaction_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
)
|
||||
|
||||
@@ -3,12 +3,21 @@
|
||||
### Public API Change
|
||||
* For users of `Statistics` objects created via `CreateDBStatistics()`, the format of the string returned by its `ToString()` method has changed.
|
||||
* With LRUCache, when high_pri_pool_ratio > 0, midpoint insertion strategy will be enabled to put low-pri items to the tail of low-pri list (the midpoint) when they first inserted into the cache. This is to make cache entries never get hit age out faster, improving cache efficiency when large background scan presents.
|
||||
* For bottommost_compression, a compatible CompressionOptions is added via `bottommost_compression_opts`. To keep backward compatible, a new boolean `enabled` is added to CompressionOptions. For compression_opts, it will be always used no matter what value of `enabled` is. For bottommost_compression_opts, it will only be used when user set `enabled=true`, otherwise, compression_opts will be used for bottommost_compression as default.
|
||||
* The "rocksdb.num.entries" table property no longer counts range deletion tombstones as entries.
|
||||
|
||||
### New Features
|
||||
* Changes the format of index blocks by storing the key in their raw form rather than converting them to InternalKey. This saves 8 bytes per index key. The feature is backward compatbile but not forward compatible. It is disabled by default unless format_version 3 or above is used.
|
||||
* Add a new table property, "rocksdb.num.range-deletions", which counts the number of range deletion tombstones in the table.
|
||||
* Improve the performance of iterators doing long range scans by using readahead, when using direct IO.
|
||||
* pin_top_level_index_and_filter (default true) in BlockBasedTableOptions can be used in combination with cache_index_and_filter_blocks to prefetch and pin the top-level index of partitioned index and filter blocks in cache. It has no impact when cache_index_and_filter_blocks is false.
|
||||
* Avoid memcpy when reading mmap files with OpenReadOnly and max_open_files==-1
|
||||
|
||||
### Bug Fixes
|
||||
* fix deadlock with enable_pipelined_write=true and max_successive_merges > 0
|
||||
* Fix corruption in non-iterator reads when mmap is used for file reads
|
||||
* Fix bug with prefix search in partition filters where a shared prefix would be ignored from the later partitions. The bug could report an eixstent key as missing. The bug could be triggered if prefix_extractor is set and partition filters is enabled.
|
||||
* Change default value of `bytes_max_delete_chunk` to 0 in NewSstFileManager() as it doesn't work well with checkpoints.
|
||||
|
||||
## 5.14.0 (5/16/2018)
|
||||
### Public API Change
|
||||
|
||||
@@ -10,9 +10,11 @@ This is the list of all known third-party language bindings for RocksDB. If some
|
||||
* PHP - https://github.com/Photonios/rocksdb-php
|
||||
* C# - https://github.com/warrenfalk/rocksdb-sharp
|
||||
* Rust
|
||||
* https://github.com/pingcap/rust-rocksdb (used in production fork of https://github.com/spacejam/rust-rocksdb)
|
||||
* https://github.com/spacejam/rust-rocksdb
|
||||
* https://github.com/bh1xuw/rust-rocks
|
||||
* D programming language - https://github.com/b1naryth1ef/rocksdb
|
||||
* Erlang - https://gitlab.com/barrel-db/erlang-rocksdb
|
||||
* Elixir - https://github.com/urbint/rox
|
||||
* Nim - https://github.com/status-im/nim-rocksdb
|
||||
* Swift and Objective-C (iOS/OSX) - https://github.com/iabudiab/ObjectiveRocks
|
||||
|
||||
@@ -85,6 +85,10 @@ ifeq ($(MAKECMDGOALS),rocksdbjavastaticrelease)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticreleasedocker)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
@@ -98,6 +102,10 @@ OPT += -momit-leaf-frame-pointer
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq (,$(shell $(CXX) -fsyntax-only -faligned-new -xc++ /dev/null 2>&1))
|
||||
CXXFLAGS += -faligned-new -DHAVE_ALIGNED_NEW
|
||||
endif
|
||||
|
||||
ifeq (,$(shell $(CXX) -fsyntax-only -maltivec -xc /dev/null 2>&1))
|
||||
CXXFLAGS += -DHAS_ALTIVEC
|
||||
CFLAGS += -DHAS_ALTIVEC
|
||||
@@ -421,6 +429,7 @@ TESTS = \
|
||||
db_table_properties_test \
|
||||
db_statistics_test \
|
||||
db_write_test \
|
||||
error_handler_test \
|
||||
autovector_test \
|
||||
blob_db_test \
|
||||
cleanable_test \
|
||||
@@ -518,6 +527,7 @@ TESTS = \
|
||||
repair_test \
|
||||
env_timed_test \
|
||||
write_prepared_transaction_test \
|
||||
write_unprepared_transaction_test \
|
||||
db_universal_compaction_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
@@ -537,6 +547,7 @@ PARALLEL_TEST = \
|
||||
table_test \
|
||||
transaction_test \
|
||||
write_prepared_transaction_test \
|
||||
write_unprepared_transaction_test \
|
||||
|
||||
# options_settable_test doesn't pass with UBSAN as we use hack in the test
|
||||
ifdef COMPILE_WITH_UBSAN
|
||||
@@ -802,7 +813,7 @@ check_0:
|
||||
| grep -E '$(tests-regexp)' \
|
||||
| build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu '{} >& t/log-{/}'
|
||||
|
||||
valgrind-blacklist-regexp = InlineSkipTest.ConcurrentInsert|TransactionTest.DeadlockStress|DBCompactionTest.SuggestCompactRangeNoTwoLevel0Compactions|BackupableDBTest.RateLimiting|DBTest.CloseSpeedup|DBTest.ThreadStatusFlush|DBTest.RateLimitingTest|DBTest.EncodeDecompressedBlockSizeTest|FaultInjectionTest.UninstalledCompaction|HarnessTest.Randomized|ExternalSSTFileTest.CompactDuringAddFileRandom|ExternalSSTFileTest.IngestFileWithGlobalSeqnoRandomized|MySQLStyleTransactionTest.TransactionStressTest
|
||||
valgrind-blacklist-regexp = InlineSkipTest.ConcurrentInsert|TransactionStressTest.DeadlockStress|DBCompactionTest.SuggestCompactRangeNoTwoLevel0Compactions|BackupableDBTest.RateLimiting|DBTest.CloseSpeedup|DBTest.ThreadStatusFlush|DBTest.RateLimitingTest|DBTest.EncodeDecompressedBlockSizeTest|FaultInjectionTest.UninstalledCompaction|HarnessTest.Randomized|ExternalSSTFileTest.CompactDuringAddFileRandom|ExternalSSTFileTest.IngestFileWithGlobalSeqnoRandomized|MySQLStyleTransactionTest.TransactionStressTest
|
||||
|
||||
.PHONY: valgrind_check_0
|
||||
valgrind_check_0:
|
||||
@@ -1184,6 +1195,9 @@ 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)
|
||||
$(AM_LINK)
|
||||
|
||||
external_sst_file_basic_test: db/external_sst_file_basic_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1474,6 +1488,9 @@ transaction_test: utilities/transactions/transaction_test.o $(LIBOBJECTS) $(TEST
|
||||
write_prepared_transaction_test: utilities/transactions/write_prepared_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
write_unprepared_transaction_test: utilities/transactions/write_unprepared_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_dump: tools/sst_dump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](http://140.211.168.68:8080/job/Rocksdb)
|
||||
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
It is built on earlier work on LevelDB by Sanjay Ghemawat (sanjay@google.com)
|
||||
and Jeff Dean (jeff@google.com)
|
||||
|
||||
@@ -28,7 +28,7 @@ rocksdb_compiler_flags = [
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
"-Wshorten-64-to-32",
|
||||
"-Wnarrowing",
|
||||
]
|
||||
|
||||
rocksdb_external_deps = [
|
||||
@@ -93,6 +93,7 @@ cpp_library(
|
||||
"db/db_info_dumper.cc",
|
||||
"db/db_iter.cc",
|
||||
"db/dbformat.cc",
|
||||
"db/error_handler.cc",
|
||||
"db/event_helpers.cc",
|
||||
"db/experimental.cc",
|
||||
"db/external_sst_file_ingestion_job.cc",
|
||||
@@ -200,6 +201,7 @@ cpp_library(
|
||||
"util/coding.cc",
|
||||
"util/compaction_job_stats_impl.cc",
|
||||
"util/comparator.cc",
|
||||
"util/compression_context_cache.cc",
|
||||
"util/concurrent_arena.cc",
|
||||
"util/crc32c.cc",
|
||||
"util/delete_scheduler.cc",
|
||||
@@ -558,13 +560,13 @@ ROCKS_TESTS = [
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_iter_test",
|
||||
"db/db_iter_test.cc",
|
||||
"db_iter_stress_test",
|
||||
"db/db_iter_stress_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"db_iter_stress_test",
|
||||
"db/db_iter_stress_test.cc",
|
||||
"db_iter_test",
|
||||
"db/db_iter_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
@@ -687,6 +689,11 @@ ROCKS_TESTS = [
|
||||
"utilities/env_timed_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"error_handler_test",
|
||||
"db/error_handler_test.cc",
|
||||
"serial",
|
||||
],
|
||||
[
|
||||
"event_logger_test",
|
||||
"util/event_logger_test.cc",
|
||||
@@ -1042,6 +1049,11 @@ ROCKS_TESTS = [
|
||||
"utilities/transactions/write_prepared_transaction_test.cc",
|
||||
"parallel",
|
||||
],
|
||||
[
|
||||
"write_unprepared_transaction_test",
|
||||
"utilities/transactions/write_unprepared_transaction_test.cc",
|
||||
"parallel",
|
||||
],
|
||||
]
|
||||
|
||||
# Generate a test rule for each entry in ROCKS_TESTS
|
||||
@@ -1071,14 +1083,3 @@ if not is_opt_mode:
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin]
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdbjavastatic",
|
||||
command = ["internal_repo_rocksdb/make_rocksdbjavastatic.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdb_lite_release",
|
||||
command = ["internal_repo_rocksdb/make_rocksdb_lite_release.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasu
|
||||
Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa. Learn more about it here: http://yahooeng.tumblr.com/post/120730204806/sherpa-scales-new-heights
|
||||
|
||||
## CockroachDB
|
||||
CockroachDB is an open-source geo-replicated transactional database (still in development). They are using RocksDB as their storage engine. Check out their github: https://github.com/cockroachdb/cockroach
|
||||
CockroachDB is an open-source geo-replicated transactional database. They are using RocksDB as their storage engine. Check out their github: https://github.com/cockroachdb/cockroach
|
||||
|
||||
## DNANexus
|
||||
DNANexus is using RocksDB to speed up processing of genomics data.
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ We plan to use this port for our business purposes here at Bing and this provide
|
||||
* All posix specific headers were replaced to port/port.h which worked well
|
||||
* Replaced `dirent.h` for `port/dirent.h` (very few places) with the implementation of the relevant interfaces within `rocksdb::port` namespace
|
||||
* Replaced `sys/time.h` to `port/sys_time.h` (few places) implemented equivalents within `rocksdb::port`
|
||||
* `printf %z` specification is not supported on Windows. To imitate existing standards we came up with a string macro `ROCKSDB_PRIszt` which expands to `%z` on posix systems and to Iu on windows.
|
||||
* `printf %z` specification is not supported on Windows. To imitate existing standards we came up with a string macro `ROCKSDB_PRIszt` which expands to `zu` on posix systems and to `Iu` on windows.
|
||||
* in class member initialization were moved to a __ctors in some cases
|
||||
* `constexpr` is not supported. We had to replace `std::numeric_limits<>::max/min()` to its C macros for constants. Sometimes we had to make class members `static const` and place a definition within a .cc file.
|
||||
* `constexpr` for functions was replaced to a template specialization (1 place)
|
||||
|
||||
@@ -3,14 +3,11 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from targets_builder import TARGETSBuilder
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import fnmatch
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from util import ColorString
|
||||
import util
|
||||
|
||||
# tests to export as libraries for inclusion in other projects
|
||||
_EXPORTED_TEST_LIBS = ["env_basic_test"]
|
||||
|
||||
@@ -32,7 +32,7 @@ rocksdb_compiler_flags = [
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
"-Wshorten-64-to-32",
|
||||
"-Wnarrowing",
|
||||
]
|
||||
|
||||
rocksdb_external_deps = [
|
||||
@@ -135,15 +135,4 @@ if not is_opt_mode:
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin]
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdbjavastatic",
|
||||
command = ["internal_repo_rocksdb/make_rocksdbjavastatic.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdb_lite_release",
|
||||
command = ["internal_repo_rocksdb/make_rocksdb_lite_release.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
"""
|
||||
|
||||
Vendored
+7
@@ -505,10 +505,17 @@ uint32_t LRUCache::GetHash(Handle* handle) const {
|
||||
|
||||
void LRUCache::DisownData() {
|
||||
// Do not drop data if compile with ASAN to suppress leak warning.
|
||||
#if defined(__clang__)
|
||||
#if !defined(__has_feature) || !__has_feature(address_sanitizer)
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
#endif
|
||||
#else // __clang__
|
||||
#ifndef __SANITIZE_ADDRESS__
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
#endif // !__SANITIZE_ADDRESS__
|
||||
#endif // __clang__
|
||||
}
|
||||
|
||||
size_t LRUCache::TEST_GetLRUSize() {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import optparse
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
+20
-10
@@ -28,6 +28,7 @@
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/block_based_table_builder.h"
|
||||
#include "table/format.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/filename.h"
|
||||
@@ -101,7 +102,7 @@ Status BuildTable(
|
||||
#endif // !ROCKSDB_LITE
|
||||
TableProperties tp;
|
||||
|
||||
if (iter->Valid() || range_del_agg->ShouldAddTombstones()) {
|
||||
if (iter->Valid() || !range_del_agg->IsEmpty()) {
|
||||
TableBuilder* builder;
|
||||
unique_ptr<WritableFileWriter> file_writer;
|
||||
{
|
||||
@@ -139,6 +140,7 @@ Status BuildTable(
|
||||
CompactionIterator c_iter(
|
||||
iter, internal_comparator.user_comparator(), &merge, kMaxSequenceNumber,
|
||||
&snapshots, earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
ShouldReportDetailedTime(env, ioptions.statistics),
|
||||
true /* internal key corruption is not ok */, range_del_agg.get());
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
@@ -154,12 +156,18 @@ Status BuildTable(
|
||||
ThreadStatus::FLUSH_BYTES_WRITTEN, IOSTATS(bytes_written));
|
||||
}
|
||||
}
|
||||
// nullptr for table_{min,max} so all range tombstones will be flushed
|
||||
range_del_agg->AddToBuilder(builder, nullptr /* lower_bound */,
|
||||
nullptr /* upper_bound */, meta);
|
||||
|
||||
for (auto it = range_del_agg->NewIterator(); it->Valid(); it->Next()) {
|
||||
auto tombstone = it->Tombstone();
|
||||
auto kv = tombstone.Serialize();
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone.SerializeEndKey(),
|
||||
tombstone.seq_, internal_comparator);
|
||||
}
|
||||
|
||||
// Finish and check for builder errors
|
||||
bool empty = builder->NumEntries() == 0;
|
||||
tp = builder->GetTableProperties();
|
||||
bool empty = builder->NumEntries() == 0 && tp.num_range_deletions == 0;
|
||||
s = c_iter.status();
|
||||
if (!s.ok() || empty) {
|
||||
builder->Abandon();
|
||||
@@ -172,7 +180,7 @@ Status BuildTable(
|
||||
meta->fd.file_size = file_size;
|
||||
meta->marked_for_compaction = builder->NeedCompact();
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder->GetTableProperties();
|
||||
tp = builder->GetTableProperties(); // refresh now that builder is finished
|
||||
if (table_properties) {
|
||||
*table_properties = tp;
|
||||
}
|
||||
@@ -196,7 +204,7 @@ Status BuildTable(
|
||||
// we will regrad this verification as user reads since the goal is
|
||||
// to cache it here for further user reads
|
||||
std::unique_ptr<InternalIterator> it(table_cache->NewIterator(
|
||||
ReadOptions(), env_options, internal_comparator, meta->fd,
|
||||
ReadOptions(), env_options, internal_comparator, *meta,
|
||||
nullptr /* range_del_agg */,
|
||||
mutable_cf_options.prefix_extractor.get(), nullptr,
|
||||
(internal_stats == nullptr) ? nullptr
|
||||
@@ -222,9 +230,11 @@ Status BuildTable(
|
||||
}
|
||||
|
||||
// Output to event logger and fire events.
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
if (!s.ok() || meta->fd.GetFileSize() > 0) {
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ using rocksdb::BackupInfo;
|
||||
using rocksdb::BackupID;
|
||||
using rocksdb::RestoreOptions;
|
||||
using rocksdb::CompactRangeOptions;
|
||||
using rocksdb::BottommostLevelCompaction;
|
||||
using rocksdb::RateLimiter;
|
||||
using rocksdb::NewGenericRateLimiter;
|
||||
using rocksdb::PinnableSlice;
|
||||
@@ -1984,6 +1985,11 @@ void rocksdb_block_based_options_set_pin_l0_filter_and_index_blocks_in_cache(
|
||||
options->rep.pin_l0_filter_and_index_blocks_in_cache = v;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_pin_top_level_index_and_filter(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char v) {
|
||||
options->rep.pin_top_level_index_and_filter = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_block_based_table_factory(
|
||||
rocksdb_options_t *opt,
|
||||
rocksdb_block_based_table_options_t* table_options) {
|
||||
@@ -2262,6 +2268,18 @@ void rocksdb_options_set_compression_per_level(rocksdb_options_t* opt,
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_bottommost_compression_options(rocksdb_options_t* opt,
|
||||
int w_bits, int level,
|
||||
int strategy,
|
||||
int max_dict_bytes,
|
||||
bool enabled) {
|
||||
opt->rep.bottommost_compression_opts.window_bits = w_bits;
|
||||
opt->rep.bottommost_compression_opts.level = level;
|
||||
opt->rep.bottommost_compression_opts.strategy = strategy;
|
||||
opt->rep.bottommost_compression_opts.max_dict_bytes = max_dict_bytes;
|
||||
opt->rep.bottommost_compression_opts.enabled = enabled;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compression_options(rocksdb_options_t* opt, int w_bits,
|
||||
int level, int strategy,
|
||||
int max_dict_bytes) {
|
||||
@@ -3109,6 +3127,11 @@ void rocksdb_compactoptions_destroy(rocksdb_compactoptions_t* opt) {
|
||||
delete opt;
|
||||
}
|
||||
|
||||
void rocksdb_compactoptions_set_bottommost_level_compaction(
|
||||
rocksdb_compactoptions_t* opt, unsigned char v) {
|
||||
opt->rep.bottommost_level_compaction = static_cast<BottommostLevelCompaction>(v);
|
||||
}
|
||||
|
||||
void rocksdb_compactoptions_set_exclusive_manual_compaction(
|
||||
rocksdb_compactoptions_t* opt, unsigned char v) {
|
||||
opt->rep.exclusive_manual_compaction = v;
|
||||
@@ -3258,6 +3281,11 @@ void rocksdb_sstfilewriter_finish(rocksdb_sstfilewriter_t* writer,
|
||||
SaveError(errptr, writer->rep->Finish(nullptr));
|
||||
}
|
||||
|
||||
void rocksdb_sstfilewriter_file_size(rocksdb_sstfilewriter_t* writer,
|
||||
uint64_t* file_size) {
|
||||
*file_size = writer->rep->FileSize();
|
||||
}
|
||||
|
||||
void rocksdb_sstfilewriter_destroy(rocksdb_sstfilewriter_t* writer) {
|
||||
delete writer->rep;
|
||||
delete writer;
|
||||
|
||||
+126
-75
@@ -57,18 +57,18 @@ class EnvCounter : public EnvWrapper {
|
||||
std::atomic<int> num_new_writable_file_;
|
||||
};
|
||||
|
||||
class ColumnFamilyTest : public testing::Test {
|
||||
class ColumnFamilyTestBase : public testing::Test {
|
||||
public:
|
||||
ColumnFamilyTest() : rnd_(139) {
|
||||
ColumnFamilyTestBase(uint32_t format) : rnd_(139), format_(format) {
|
||||
env_ = new EnvCounter(Env::Default());
|
||||
dbname_ = test::TmpDir() + "/column_family_test";
|
||||
dbname_ = test::PerThreadDBPath("column_family_test");
|
||||
db_options_.create_if_missing = true;
|
||||
db_options_.fail_if_options_file_error = true;
|
||||
db_options_.env = env_;
|
||||
DestroyDB(dbname_, Options(db_options_, column_family_options_));
|
||||
}
|
||||
|
||||
~ColumnFamilyTest() {
|
||||
virtual ~ColumnFamilyTestBase() {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (auto h : handles_) {
|
||||
ColumnFamilyDescriptor cfdescriptor;
|
||||
@@ -81,6 +81,12 @@ class ColumnFamilyTest : public testing::Test {
|
||||
delete env_;
|
||||
}
|
||||
|
||||
BlockBasedTableOptions GetBlockBasedTableOptions() {
|
||||
BlockBasedTableOptions options;
|
||||
options.format_version = format_;
|
||||
return options;
|
||||
}
|
||||
|
||||
// Return the value to associate with the specified key
|
||||
Slice Value(int k, std::string* storage) {
|
||||
if (k == 0) {
|
||||
@@ -516,9 +522,22 @@ class ColumnFamilyTest : public testing::Test {
|
||||
DB* db_ = nullptr;
|
||||
EnvCounter* env_;
|
||||
Random rnd_;
|
||||
uint32_t format_;
|
||||
};
|
||||
|
||||
TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
class ColumnFamilyTest
|
||||
: public ColumnFamilyTestBase,
|
||||
virtual public ::testing::WithParamInterface<uint32_t> {
|
||||
public:
|
||||
ColumnFamilyTest() : ColumnFamilyTestBase(GetParam()) {}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FormatDef, ColumnFamilyTest,
|
||||
testing::Values(test::kDefaultFormatVersion));
|
||||
INSTANTIATE_TEST_CASE_P(FormatLatest, ColumnFamilyTest,
|
||||
testing::Values(test::kLatestFormatVersion));
|
||||
|
||||
TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
for (int iter = 0; iter < 3; ++iter) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two", "three"});
|
||||
@@ -537,7 +556,8 @@ TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
Reopen();
|
||||
}
|
||||
CreateColumnFamilies({"three2"});
|
||||
// ID 3 that was used for dropped column family "three" should not be reused
|
||||
// ID 3 that was used for dropped column family "three" should not be
|
||||
// reused
|
||||
auto cfh3 = reinterpret_cast<ColumnFamilyHandleImpl*>(handles_[3]);
|
||||
ASSERT_EQ(4U, cfh3->GetID());
|
||||
Close();
|
||||
@@ -546,7 +566,7 @@ TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) {
|
||||
TEST_P(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) {
|
||||
Open();
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
@@ -569,10 +589,13 @@ TEST_F(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class FlushEmptyCFTestWithParam : public ColumnFamilyTest,
|
||||
public testing::WithParamInterface<bool> {
|
||||
class FlushEmptyCFTestWithParam
|
||||
: public ColumnFamilyTestBase,
|
||||
virtual public testing::WithParamInterface<std::tuple<uint32_t, bool>> {
|
||||
public:
|
||||
FlushEmptyCFTestWithParam() { allow_2pc_ = GetParam(); }
|
||||
FlushEmptyCFTestWithParam()
|
||||
: ColumnFamilyTestBase(std::get<0>(GetParam())),
|
||||
allow_2pc_(std::get<1>(GetParam())) {}
|
||||
|
||||
// Required if inheriting from testing::WithParamInterface<>
|
||||
static void SetUpTestCase() {}
|
||||
@@ -697,10 +720,16 @@ TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest2) {
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FlushEmptyCFTestWithParam, FlushEmptyCFTestWithParam,
|
||||
::testing::Bool());
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, FlushEmptyCFTestWithParam,
|
||||
testing::Values(std::make_tuple(test::kDefaultFormatVersion, true),
|
||||
std::make_tuple(test::kDefaultFormatVersion, false)));
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, FlushEmptyCFTestWithParam,
|
||||
testing::Values(std::make_tuple(test::kLatestFormatVersion, true),
|
||||
std::make_tuple(test::kLatestFormatVersion, false)));
|
||||
|
||||
TEST_F(ColumnFamilyTest, AddDrop) {
|
||||
TEST_P(ColumnFamilyTest, AddDrop) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two", "three"});
|
||||
ASSERT_EQ("NOT_FOUND", Get(1, "fodor"));
|
||||
@@ -726,7 +755,7 @@ TEST_F(ColumnFamilyTest, AddDrop) {
|
||||
std::vector<std::string>({"default", "four", "three"}));
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, BulkAddDrop) {
|
||||
TEST_P(ColumnFamilyTest, BulkAddDrop) {
|
||||
constexpr int kNumCF = 1000;
|
||||
ColumnFamilyOptions cf_options;
|
||||
WriteOptions write_options;
|
||||
@@ -764,7 +793,7 @@ TEST_F(ColumnFamilyTest, BulkAddDrop) {
|
||||
ASSERT_TRUE(families == std::vector<std::string>({"default"}));
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, DropTest) {
|
||||
TEST_P(ColumnFamilyTest, DropTest) {
|
||||
// first iteration - dont reopen DB before dropping
|
||||
// second iteration - reopen DB before dropping
|
||||
for (int iter = 0; iter < 2; ++iter) {
|
||||
@@ -788,7 +817,7 @@ TEST_F(ColumnFamilyTest, DropTest) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, WriteBatchFailure) {
|
||||
TEST_P(ColumnFamilyTest, WriteBatchFailure) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
WriteBatch batch;
|
||||
@@ -806,7 +835,7 @@ TEST_F(ColumnFamilyTest, WriteBatchFailure) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, ReadWrite) {
|
||||
TEST_P(ColumnFamilyTest, ReadWrite) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
ASSERT_OK(Put(0, "foo", "v1"));
|
||||
@@ -830,7 +859,7 @@ TEST_F(ColumnFamilyTest, ReadWrite) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, IgnoreRecoveredLog) {
|
||||
TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) {
|
||||
std::string backup_logs = dbname_ + "/backup_logs";
|
||||
|
||||
// delete old files in backup_logs directory
|
||||
@@ -906,7 +935,7 @@ TEST_F(ColumnFamilyTest, IgnoreRecoveredLog) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // TEST functions used are not supported
|
||||
TEST_F(ColumnFamilyTest, FlushTest) {
|
||||
TEST_P(ColumnFamilyTest, FlushTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
ASSERT_OK(Put(0, "foo", "v1"));
|
||||
@@ -954,7 +983,7 @@ TEST_F(ColumnFamilyTest, FlushTest) {
|
||||
}
|
||||
|
||||
// Makes sure that obsolete log files get deleted
|
||||
TEST_F(ColumnFamilyTest, LogDeletionTest) {
|
||||
TEST_P(ColumnFamilyTest, LogDeletionTest) {
|
||||
db_options_.max_total_wal_size = std::numeric_limits<uint64_t>::max();
|
||||
column_family_options_.arena_block_size = 4 * 1024;
|
||||
column_family_options_.write_buffer_size = 128000; // 128KB
|
||||
@@ -1022,7 +1051,7 @@ TEST_F(ColumnFamilyTest, LogDeletionTest) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(ColumnFamilyTest, CrashAfterFlush) {
|
||||
TEST_P(ColumnFamilyTest, CrashAfterFlush) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
db_options_.env = fault_env.get();
|
||||
@@ -1054,7 +1083,7 @@ TEST_F(ColumnFamilyTest, CrashAfterFlush) {
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, OpenNonexistentColumnFamily) {
|
||||
TEST_P(ColumnFamilyTest, OpenNonexistentColumnFamily) {
|
||||
ASSERT_OK(TryOpen({"default"}));
|
||||
Close();
|
||||
ASSERT_TRUE(TryOpen({"default", "dne"}).IsInvalidArgument());
|
||||
@@ -1062,7 +1091,7 @@ TEST_F(ColumnFamilyTest, OpenNonexistentColumnFamily) {
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
|
||||
// Makes sure that obsolete log files get deleted
|
||||
TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
// disable flushing stale column families
|
||||
db_options_.max_total_wal_size = std::numeric_limits<uint64_t>::max();
|
||||
Open();
|
||||
@@ -1168,7 +1197,7 @@ TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // Cuckoo is not supported in lite
|
||||
TEST_F(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
TEST_P(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
db_options_.allow_concurrent_memtable_write = false;
|
||||
Open();
|
||||
auto* s1 = dbfull()->GetSnapshot();
|
||||
@@ -1206,7 +1235,7 @@ static TestComparator third_comparator;
|
||||
static TestComparator fourth_comparator;
|
||||
|
||||
// Test that we can retrieve the comparator from a created CF
|
||||
TEST_F(ColumnFamilyTest, GetComparator) {
|
||||
TEST_P(ColumnFamilyTest, GetComparator) {
|
||||
Open();
|
||||
// Add a column family with no comparator specified
|
||||
CreateColumnFamilies({"first"});
|
||||
@@ -1225,7 +1254,7 @@ TEST_F(ColumnFamilyTest, GetComparator) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, DifferentMergeOperators) {
|
||||
TEST_P(ColumnFamilyTest, DifferentMergeOperators) {
|
||||
Open();
|
||||
CreateColumnFamilies({"first", "second"});
|
||||
ColumnFamilyOptions default_cf, first, second;
|
||||
@@ -1256,7 +1285,7 @@ TEST_F(ColumnFamilyTest, DifferentMergeOperators) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
|
||||
TEST_F(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
@@ -1268,7 +1297,7 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = static_cast<uint64_t>(1) << 60;
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1328,7 +1357,7 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Sync points not supported in RocksDB Lite
|
||||
|
||||
TEST_F(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
@@ -1340,7 +1369,7 @@ TEST_F(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1422,10 +1451,12 @@ TEST_F(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
@@ -1437,7 +1468,8 @@ TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1516,9 +1548,11 @@ TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
@@ -1530,7 +1564,8 @@ TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1612,9 +1647,11 @@ TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
TEST_P(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
@@ -1626,7 +1663,8 @@ TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1711,9 +1749,11 @@ TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
@@ -1725,7 +1765,8 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1801,9 +1842,11 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
TEST_P(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
@@ -1815,7 +1858,8 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1891,6 +1935,8 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
// In this test, we generate enough files to trigger automatic compactions.
|
||||
@@ -1899,7 +1945,7 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
// This will wait because the automatic compaction has files it needs.
|
||||
// Once the conflict is hit, the automatic compaction starts and ends
|
||||
// Then the manual will run and end.
|
||||
TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
@@ -1911,7 +1957,8 @@ TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1980,6 +2027,8 @@ TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -1996,7 +2045,7 @@ std::string IterStatus(Iterator* iter) {
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_F(ColumnFamilyTest, NewIteratorsTest) {
|
||||
TEST_P(ColumnFamilyTest, NewIteratorsTest) {
|
||||
// iter == 0 -- no tailing
|
||||
// iter == 2 -- tailing
|
||||
for (int iter = 0; iter < 2; ++iter) {
|
||||
@@ -2043,7 +2092,7 @@ TEST_F(ColumnFamilyTest, NewIteratorsTest) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // ReadOnlyDB is not supported
|
||||
TEST_F(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
TEST_P(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two", "three", "four"});
|
||||
ASSERT_OK(Put(0, "a", "b"));
|
||||
@@ -2095,7 +2144,7 @@ TEST_F(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported in lite
|
||||
TEST_F(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
TEST_P(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two", "three", "four"});
|
||||
|
||||
@@ -2119,7 +2168,7 @@ TEST_F(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForCompaction() is not supported in lite
|
||||
TEST_F(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
@@ -2154,7 +2203,7 @@ TEST_F(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
Status s = TryOpen({"one", "two"});
|
||||
ASSERT_TRUE(!s.ok());
|
||||
db_options_.create_missing_column_families = true;
|
||||
@@ -2163,7 +2212,7 @@ TEST_F(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, SanitizeOptions) {
|
||||
TEST_P(ColumnFamilyTest, SanitizeOptions) {
|
||||
DBOptions db_options;
|
||||
for (int s = kCompactionStyleLevel; s <= kCompactionStyleUniversal; ++s) {
|
||||
for (int l = 0; l <= 2; l++) {
|
||||
@@ -2212,7 +2261,7 @@ TEST_F(ColumnFamilyTest, SanitizeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, ReadDroppedColumnFamily) {
|
||||
TEST_P(ColumnFamilyTest, ReadDroppedColumnFamily) {
|
||||
// iter 0 -- drop CF, don't reopen
|
||||
// iter 1 -- delete CF, reopen
|
||||
for (int iter = 0; iter < 2; ++iter) {
|
||||
@@ -2284,7 +2333,7 @@ TEST_F(ColumnFamilyTest, ReadDroppedColumnFamily) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, FlushAndDropRaceCondition) {
|
||||
TEST_P(ColumnFamilyTest, FlushAndDropRaceCondition) {
|
||||
db_options_.create_missing_column_families = true;
|
||||
Open({"default", "one"});
|
||||
ColumnFamilyOptions options;
|
||||
@@ -2350,12 +2399,13 @@ TEST_F(ColumnFamilyTest, FlushAndDropRaceCondition) {
|
||||
// skipped as persisting options is not supported in ROCKSDB_LITE
|
||||
namespace {
|
||||
std::atomic<int> test_stage(0);
|
||||
std::atomic<bool> ordered_by_writethread(false);
|
||||
const int kMainThreadStartPersistingOptionsFile = 1;
|
||||
const int kChildThreadFinishDroppingColumnFamily = 2;
|
||||
const int kChildThreadWaitingMainThreadPersistOptions = 3;
|
||||
void DropSingleColumnFamily(ColumnFamilyTest* cf_test, int cf_id,
|
||||
std::vector<Comparator*>* comparators) {
|
||||
while (test_stage < kMainThreadStartPersistingOptionsFile) {
|
||||
while (test_stage < kMainThreadStartPersistingOptionsFile &&
|
||||
!ordered_by_writethread) {
|
||||
Env::Default()->SleepForMicroseconds(100);
|
||||
}
|
||||
cf_test->DropColumnFamilies({cf_id});
|
||||
@@ -2367,7 +2417,7 @@ void DropSingleColumnFamily(ColumnFamilyTest* cf_test, int cf_id,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(ColumnFamilyTest, CreateAndDropRace) {
|
||||
TEST_P(ColumnFamilyTest, CreateAndDropRace) {
|
||||
const int kCfCount = 5;
|
||||
std::vector<ColumnFamilyOptions> cf_opts;
|
||||
std::vector<Comparator*> comparators;
|
||||
@@ -2388,7 +2438,8 @@ TEST_F(ColumnFamilyTest, CreateAndDropRace) {
|
||||
// will be blocked until some other thread update the test_stage.
|
||||
if (main_thread_id == current_thread_id) {
|
||||
test_stage = kMainThreadStartPersistingOptionsFile;
|
||||
while (test_stage < kChildThreadFinishDroppingColumnFamily) {
|
||||
while (test_stage < kChildThreadFinishDroppingColumnFamily &&
|
||||
!ordered_by_writethread) {
|
||||
Env::Default()->SleepForMicroseconds(100);
|
||||
}
|
||||
}
|
||||
@@ -2399,11 +2450,7 @@ TEST_F(ColumnFamilyTest, CreateAndDropRace) {
|
||||
// This means a thread doing DropColumnFamily() is waiting for
|
||||
// other thread to finish persisting options.
|
||||
// In such case, we update the test_stage to unblock the main thread.
|
||||
test_stage = kChildThreadWaitingMainThreadPersistOptions;
|
||||
|
||||
// Note that based on the test setting, this must not be the
|
||||
// main thread.
|
||||
ASSERT_NE(main_thread_id, std::this_thread::get_id());
|
||||
ordered_by_writethread = true;
|
||||
});
|
||||
|
||||
// Create a database with four column families
|
||||
@@ -2414,7 +2461,8 @@ TEST_F(ColumnFamilyTest, CreateAndDropRace) {
|
||||
|
||||
// Start a thread that will drop the first column family
|
||||
// and its comparator
|
||||
rocksdb::port::Thread drop_cf_thread(DropSingleColumnFamily, this, 1, &comparators);
|
||||
rocksdb::port::Thread drop_cf_thread(DropSingleColumnFamily, this, 1,
|
||||
&comparators);
|
||||
|
||||
DropColumnFamilies({2});
|
||||
|
||||
@@ -2426,10 +2474,13 @@ TEST_F(ColumnFamilyTest, CreateAndDropRace) {
|
||||
delete comparator;
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
TEST_P(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
const uint64_t kBaseRate = 800000u;
|
||||
db_options_.delayed_write_rate = kBaseRate;
|
||||
db_options_.max_background_compactions = 6;
|
||||
@@ -2611,7 +2662,7 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate());
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, CompactionSpeedupSingleColumnFamily) {
|
||||
TEST_P(ColumnFamilyTest, CompactionSpeedupSingleColumnFamily) {
|
||||
db_options_.max_background_compactions = 6;
|
||||
Open({"default"});
|
||||
ColumnFamilyData* cfd =
|
||||
@@ -2675,7 +2726,7 @@ TEST_F(ColumnFamilyTest, CompactionSpeedupSingleColumnFamily) {
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, WriteStallTwoColumnFamilies) {
|
||||
TEST_P(ColumnFamilyTest, WriteStallTwoColumnFamilies) {
|
||||
const uint64_t kBaseRate = 810000u;
|
||||
db_options_.delayed_write_rate = kBaseRate;
|
||||
Open();
|
||||
@@ -2750,7 +2801,7 @@ TEST_F(ColumnFamilyTest, WriteStallTwoColumnFamilies) {
|
||||
ASSERT_EQ(kBaseRate / 1.25, GetDbDelayedWriteRate());
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) {
|
||||
TEST_P(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) {
|
||||
db_options_.max_background_compactions = 6;
|
||||
column_family_options_.soft_pending_compaction_bytes_limit = 200;
|
||||
column_family_options_.hard_pending_compaction_bytes_limit = 2000;
|
||||
@@ -2815,7 +2866,7 @@ TEST_F(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) {
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, CreateAndDestoryOptions) {
|
||||
TEST_P(ColumnFamilyTest, CreateAndDestoryOptions) {
|
||||
std::unique_ptr<ColumnFamilyOptions> cfo(new ColumnFamilyOptions());
|
||||
ColumnFamilyHandle* cfh;
|
||||
Open();
|
||||
@@ -2827,7 +2878,7 @@ TEST_F(ColumnFamilyTest, CreateAndDestoryOptions) {
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, CreateDropAndDestroy) {
|
||||
TEST_P(ColumnFamilyTest, CreateDropAndDestroy) {
|
||||
ColumnFamilyHandle* cfh;
|
||||
Open();
|
||||
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh));
|
||||
@@ -2838,7 +2889,7 @@ TEST_F(ColumnFamilyTest, CreateDropAndDestroy) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) {
|
||||
TEST_P(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) {
|
||||
ColumnFamilyHandle* cfh;
|
||||
Open();
|
||||
ASSERT_OK(db_->CreateColumnFamily(ColumnFamilyOptions(), "yoyo", &cfh));
|
||||
@@ -2849,7 +2900,7 @@ TEST_F(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) {
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
TEST_P(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
SpecialEnv env(Env::Default());
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
@@ -2891,7 +2942,7 @@ TEST_F(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
|
||||
TEST_F(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
SpecialEnv env(Env::Default());
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
@@ -2936,7 +2987,7 @@ TEST_F(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
SpecialEnv env(Env::Default());
|
||||
// Allow both of flush and purge job to schedule.
|
||||
env.SetBackgroundThreads(2, Env::HIGH);
|
||||
@@ -2993,7 +3044,7 @@ TEST_F(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // TEST functions are not supported in lite
|
||||
TEST_F(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
SpecialEnv env(Env::Default());
|
||||
// Allow both of flush and purge job to schedule.
|
||||
env.SetBackgroundThreads(2, Env::HIGH);
|
||||
@@ -3070,7 +3121,7 @@ TEST_F(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
// Disable on windows because SyncWAL requires env->IsSyncThreadSafe()
|
||||
// to return true which is not so in unbuffered mode.
|
||||
#ifndef OS_WIN
|
||||
TEST_F(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
TEST_P(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
|
||||
@@ -3105,7 +3156,7 @@ TEST_F(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
// test is being used to ensure a roll of wal files.
|
||||
// Basic idea is to test that WAL truncation is being detected and not
|
||||
// ignored
|
||||
TEST_F(ColumnFamilyTest, DISABLED_LogTruncationTest) {
|
||||
TEST_P(ColumnFamilyTest, DISABLED_LogTruncationTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
|
||||
@@ -3175,7 +3226,7 @@ TEST_F(ColumnFamilyTest, DISABLED_LogTruncationTest) {
|
||||
env_->DeleteDir(backup_logs);
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, DefaultCfPathsTest) {
|
||||
TEST_P(ColumnFamilyTest, DefaultCfPathsTest) {
|
||||
Open();
|
||||
// Leave cf_paths for one column families to be empty.
|
||||
// Files should be generated according to db_paths for that
|
||||
@@ -3202,7 +3253,7 @@ TEST_F(ColumnFamilyTest, DefaultCfPathsTest) {
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, MultipleCFPathsTest) {
|
||||
TEST_P(ColumnFamilyTest, MultipleCFPathsTest) {
|
||||
Open();
|
||||
// Configure Column family specific paths.
|
||||
ColumnFamilyOptions cf_opt1, cf_opt2;
|
||||
|
||||
@@ -24,7 +24,7 @@ class CompactFilesTest : public testing::Test {
|
||||
public:
|
||||
CompactFilesTest() {
|
||||
env_ = Env::Default();
|
||||
db_name_ = test::TmpDir(env_) + "/compact_files_test";
|
||||
db_name_ = test::PerThreadDBPath("compact_files_test");
|
||||
}
|
||||
|
||||
std::string db_name_;
|
||||
|
||||
@@ -134,6 +134,7 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
int _output_level, uint64_t _target_file_size,
|
||||
uint64_t _max_compaction_bytes, uint32_t _output_path_id,
|
||||
CompressionType _compression,
|
||||
CompressionOptions _compression_opts,
|
||||
uint32_t _max_subcompactions,
|
||||
std::vector<FileMetaData*> _grandparents,
|
||||
bool _manual_compaction, double _score,
|
||||
@@ -152,6 +153,7 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
cfd_(nullptr),
|
||||
output_path_id_(_output_path_id),
|
||||
output_compression_(_compression),
|
||||
output_compression_opts_(_compression_opts),
|
||||
deletion_compaction_(_deletion_compaction),
|
||||
inputs_(std::move(_inputs)),
|
||||
grandparents_(std::move(_grandparents)),
|
||||
|
||||
+7
-1
@@ -40,7 +40,7 @@ class Compaction {
|
||||
std::vector<CompactionInputFiles> inputs, int output_level,
|
||||
uint64_t target_file_size, uint64_t max_compaction_bytes,
|
||||
uint32_t output_path_id, CompressionType compression,
|
||||
uint32_t max_subcompactions,
|
||||
CompressionOptions compression_opts, uint32_t max_subcompactions,
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
bool manual_compaction = false, double score = -1,
|
||||
bool deletion_compaction = false,
|
||||
@@ -119,6 +119,11 @@ class Compaction {
|
||||
// What compression for output
|
||||
CompressionType output_compression() const { return output_compression_; }
|
||||
|
||||
// What compression options for output
|
||||
CompressionOptions output_compression_opts() const {
|
||||
return output_compression_opts_;
|
||||
}
|
||||
|
||||
// Whether need to write output file to second DB path.
|
||||
uint32_t output_path_id() const { return output_path_id_; }
|
||||
|
||||
@@ -283,6 +288,7 @@ class Compaction {
|
||||
|
||||
const uint32_t output_path_id_;
|
||||
CompressionType output_compression_;
|
||||
CompressionOptions output_compression_opts_;
|
||||
// If true, then the comaction can be done by simply deleting input files.
|
||||
const bool deletion_compaction_;
|
||||
|
||||
|
||||
@@ -17,14 +17,15 @@ CompactionIterator::CompactionIterator(
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool expect_valid_internal_key, RangeDelAggregator* range_del_agg,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
expect_valid_internal_key, range_del_agg,
|
||||
report_detailed_time, expect_valid_internal_key, range_del_agg,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new CompactionProxy(compaction) : nullptr),
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum) {}
|
||||
@@ -34,7 +35,8 @@ CompactionIterator::CompactionIterator(
|
||||
SequenceNumber /*last_sequence*/, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool expect_valid_internal_key, RangeDelAggregator* range_del_agg,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
@@ -46,6 +48,7 @@ CompactionIterator::CompactionIterator(
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
env_(env),
|
||||
report_detailed_time_(report_detailed_time),
|
||||
expect_valid_internal_key_(expect_valid_internal_key),
|
||||
range_del_agg_(range_del_agg),
|
||||
compaction_(std::move(compaction)),
|
||||
@@ -171,12 +174,12 @@ void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
// to get sequence number.
|
||||
Slice& filter_key = ikey_.type == kTypeValue ? ikey_.user_key : key_;
|
||||
{
|
||||
StopWatchNano timer(env_, true);
|
||||
StopWatchNano timer(env_, report_detailed_time_);
|
||||
filter = compaction_filter_->FilterV2(
|
||||
compaction_->level(), filter_key, value_type, value_,
|
||||
&compaction_filter_value_, compaction_filter_skip_until_.rep());
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr ? timer.ElapsedNanos() : 0;
|
||||
env_ != nullptr && report_detailed_time_ ? timer.ElapsedNanos() : 0;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
@@ -551,7 +554,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// 1. new user key -OR-
|
||||
// 2. different snapshot stripe
|
||||
bool should_delete = range_del_agg_->ShouldDelete(
|
||||
key_, RangeDelAggregator::RangePositioningMode::kForwardTraversal);
|
||||
key_, RangeDelPositioningMode::kForwardTraversal);
|
||||
if (should_delete) {
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_range_del;
|
||||
|
||||
@@ -63,7 +63,7 @@ class CompactionIterator {
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool expect_valid_internal_key,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
@@ -76,7 +76,7 @@ class CompactionIterator {
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool expect_valid_internal_key,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
@@ -139,6 +139,7 @@ class CompactionIterator {
|
||||
const SequenceNumber earliest_write_conflict_snapshot_;
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
Env* env_;
|
||||
bool report_detailed_time_;
|
||||
bool expect_valid_internal_key_;
|
||||
RangeDelAggregator* range_del_agg_;
|
||||
std::unique_ptr<CompactionProxy> compaction_;
|
||||
|
||||
@@ -247,8 +247,9 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_,
|
||||
earliest_write_conflict_snapshot, snapshot_checker_.get(),
|
||||
Env::Default(), false, range_del_agg_.get(), std::move(compaction),
|
||||
filter, &shutting_down_));
|
||||
Env::Default(), false /* report_detailed_time */,
|
||||
false, range_del_agg_.get(), std::move(compaction), filter,
|
||||
&shutting_down_));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
|
||||
+166
-66
@@ -25,8 +25,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
@@ -307,7 +309,7 @@ CompactionJob::CompactionJob(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, Status* db_bg_error,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, std::shared_ptr<Cache> table_cache,
|
||||
@@ -331,7 +333,7 @@ CompactionJob::CompactionJob(
|
||||
output_directory_(output_directory),
|
||||
stats_(stats),
|
||||
db_mutex_(db_mutex),
|
||||
db_bg_error_(db_bg_error),
|
||||
db_error_handler_(db_error_handler),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
@@ -607,6 +609,69 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
thread_pool.clear();
|
||||
std::vector<const FileMetaData*> files_meta;
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
for (const auto& output : state.outputs) {
|
||||
files_meta.emplace_back(&output.meta);
|
||||
}
|
||||
}
|
||||
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
|
||||
auto prefix_extractor =
|
||||
compact_->compaction->mutable_cf_options()->prefix_extractor.get();
|
||||
std::atomic<size_t> next_file_meta_idx(0);
|
||||
auto verify_table = [&](Status& output_status) {
|
||||
while (true) {
|
||||
size_t file_idx = next_file_meta_idx.fetch_add(1);
|
||||
if (file_idx >= files_meta.size()) {
|
||||
break;
|
||||
}
|
||||
// Verify that the table is usable
|
||||
// We set for_compaction to false and don't OptimizeForCompactionTableRead
|
||||
// here because this is a special case after we finish the table building
|
||||
// No matter whether use_direct_io_for_flush_and_compaction is true,
|
||||
// we will regard this verification as user reads since the goal is
|
||||
// to cache it here for further user reads
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(),
|
||||
*files_meta[file_idx], nullptr /* range_del_agg */,
|
||||
prefix_extractor, nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
false, nullptr /* arena */, false /* skip_filters */,
|
||||
compact_->compaction->output_level());
|
||||
auto s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {}
|
||||
s = iter->status();
|
||||
}
|
||||
|
||||
delete iter;
|
||||
|
||||
if (!s.ok()) {
|
||||
output_status = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) {
|
||||
thread_pool.emplace_back(verify_table,
|
||||
std::ref(compact_->sub_compact_states[i].status));
|
||||
}
|
||||
verify_table(compact_->sub_compact_states[0].status);
|
||||
for (auto& thread : thread_pool) {
|
||||
thread.join();
|
||||
}
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
if (!state.status.ok()) {
|
||||
status = state.status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TablePropertiesCollection tp;
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
for (const auto& output : state.outputs) {
|
||||
@@ -760,10 +825,13 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// zstd's dictionary trainer, or just use them directly. Then, the dictionary
|
||||
// is used for compressing subsequent output files in the same subcompaction.
|
||||
const bool kUseZstdTrainer =
|
||||
cfd->ioptions()->compression_opts.zstd_max_train_bytes > 0;
|
||||
sub_compact->compaction->output_compression_opts().zstd_max_train_bytes >
|
||||
0;
|
||||
const size_t kSampleBytes =
|
||||
kUseZstdTrainer ? cfd->ioptions()->compression_opts.zstd_max_train_bytes
|
||||
: cfd->ioptions()->compression_opts.max_dict_bytes;
|
||||
kUseZstdTrainer
|
||||
? sub_compact->compaction->output_compression_opts()
|
||||
.zstd_max_train_bytes
|
||||
: sub_compact->compaction->output_compression_opts().max_dict_bytes;
|
||||
const int kSampleLenShift = 6; // 2^6 = 64-byte samples
|
||||
std::set<size_t> sample_begin_offsets;
|
||||
if (bottommost_level_ && kSampleBytes > 0) {
|
||||
@@ -816,9 +884,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
sub_compact->c_iter.reset(new CompactionIterator(
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, false, range_del_agg.get(),
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
preserve_deletes_seqnum_));
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_), false,
|
||||
range_del_agg.get(), sub_compact->compaction, compaction_filter,
|
||||
shutting_down_, preserve_deletes_seqnum_));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) {
|
||||
@@ -966,7 +1034,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (kUseZstdTrainer) {
|
||||
sub_compact->compression_dict = ZSTD_TrainDictionary(
|
||||
dict_sample_data, kSampleLenShift,
|
||||
cfd->ioptions()->compression_opts.max_dict_bytes);
|
||||
sub_compact->compaction->output_compression_opts()
|
||||
.max_dict_bytes);
|
||||
} else {
|
||||
sub_compact->compression_dict = std::move(dict_sample_data);
|
||||
}
|
||||
@@ -1006,8 +1075,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
if (status.ok() && sub_compact->builder == nullptr &&
|
||||
sub_compact->outputs.size() == 0 &&
|
||||
range_del_agg->ShouldAddTombstones(bottommost_level_)) {
|
||||
sub_compact->outputs.size() == 0) {
|
||||
// handle subcompaction containing only range deletions
|
||||
status = OpenCompactionOutputFile(sub_compact);
|
||||
}
|
||||
@@ -1095,9 +1163,13 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
uint64_t output_number = sub_compact->current_output()->meta.fd.GetNumber();
|
||||
assert(output_number != 0);
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
const Comparator* ucmp = cfd->user_comparator();
|
||||
|
||||
// Check for iterator errors
|
||||
Status s = input_status;
|
||||
auto meta = &sub_compact->current_output()->meta;
|
||||
assert(meta != nullptr);
|
||||
if (s.ok()) {
|
||||
Slice lower_bound_guard, upper_bound_guard;
|
||||
std::string smallest_user_key;
|
||||
@@ -1126,9 +1198,71 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// subcompaction ends.
|
||||
upper_bound = sub_compact->end;
|
||||
}
|
||||
range_del_agg->AddToBuilder(sub_compact->builder.get(), lower_bound,
|
||||
upper_bound, meta, range_del_out_stats,
|
||||
bottommost_level_);
|
||||
auto earliest_snapshot = kMaxSequenceNumber;
|
||||
if (existing_snapshots_.size() > 0) {
|
||||
earliest_snapshot = existing_snapshots_[0];
|
||||
}
|
||||
auto it = range_del_agg->NewIterator();
|
||||
if (lower_bound != nullptr) {
|
||||
it->Seek(*lower_bound);
|
||||
}
|
||||
for (; it->Valid(); it->Next()) {
|
||||
auto tombstone = it->Tombstone();
|
||||
if (upper_bound != nullptr &&
|
||||
ucmp->Compare(*upper_bound, tombstone.start_key_) <= 0) {
|
||||
// Tombstones starting at upper_bound or later only need to be included
|
||||
// in the next table. Break because subsequent tombstones will start
|
||||
// even later.
|
||||
break;
|
||||
}
|
||||
|
||||
if (bottommost_level_ && tombstone.seq_ <= earliest_snapshot) {
|
||||
// TODO(andrewkr): tombstones that span multiple output files are
|
||||
// counted for each compaction output file, so lots of double counting.
|
||||
range_del_out_stats->num_range_del_drop_obsolete++;
|
||||
range_del_out_stats->num_record_drop_obsolete++;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto kv = tombstone.Serialize();
|
||||
sub_compact->builder->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey smallest_candidate = std::move(kv.first);
|
||||
if (lower_bound != nullptr &&
|
||||
ucmp->Compare(smallest_candidate.user_key(), *lower_bound) <= 0) {
|
||||
// Pretend the smallest key has the same user key as lower_bound
|
||||
// (the max key in the previous table or subcompaction) in order for
|
||||
// files to appear key-space partitioned.
|
||||
//
|
||||
// Choose lowest seqnum so this file's smallest internal key comes
|
||||
// after the previous file's/subcompaction's largest. The fake seqnum
|
||||
// is OK because the read path's file-picking code only considers user
|
||||
// key.
|
||||
smallest_candidate = InternalKey(*lower_bound, 0, kTypeRangeDeletion);
|
||||
}
|
||||
InternalKey largest_candidate = tombstone.SerializeEndKey();
|
||||
if (upper_bound != nullptr &&
|
||||
ucmp->Compare(*upper_bound, largest_candidate.user_key()) <= 0) {
|
||||
// Pretend the largest key has the same user key as upper_bound (the
|
||||
// min key in the following table or subcompaction) in order for files
|
||||
// to appear key-space partitioned.
|
||||
//
|
||||
// Choose highest seqnum so this file's largest internal key comes
|
||||
// before the next file's/subcompaction's smallest. The fake seqnum is
|
||||
// OK because the read path's file-picking code only considers the user
|
||||
// key portion.
|
||||
//
|
||||
// Note Seek() also creates InternalKey with (user_key,
|
||||
// kMaxSequenceNumber), but with kTypeDeletion (0x7) instead of
|
||||
// kTypeRangeDeletion (0xF), so the range tombstone comes before the
|
||||
// Seek() key in InternalKey's ordering. So Seek() will look in the
|
||||
// next file for the user key.
|
||||
largest_candidate =
|
||||
InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
}
|
||||
meta->UpdateBoundariesForRange(smallest_candidate, largest_candidate,
|
||||
tombstone.seq_,
|
||||
cfd->internal_comparator());
|
||||
}
|
||||
meta->marked_for_compaction = sub_compact->builder->NeedCompact();
|
||||
}
|
||||
const uint64_t current_entries = sub_compact->builder->NumEntries();
|
||||
@@ -1154,7 +1288,12 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
sub_compact->outfile.reset();
|
||||
|
||||
if (s.ok() && current_entries == 0) {
|
||||
TableProperties tp;
|
||||
if (s.ok()) {
|
||||
tp = sub_compact->builder->GetTableProperties();
|
||||
}
|
||||
|
||||
if (s.ok() && current_entries == 0 && tp.num_range_deletions == 0) {
|
||||
// If there is nothing to output, no necessary to generate a sst file.
|
||||
// This happens when the output level is bottom level, at the same time
|
||||
// the sub_compact output nothing.
|
||||
@@ -1172,46 +1311,16 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
return s;
|
||||
}
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
TableProperties tp;
|
||||
if (s.ok() && current_entries > 0) {
|
||||
// Verify that the table is usable
|
||||
// We set for_compaction to false and don't OptimizeForCompactionTableRead
|
||||
// here because this is a special case after we finish the table building
|
||||
// No matter whether use_direct_io_for_flush_and_compaction is true,
|
||||
// we will regard this verification as user reads since the goal is
|
||||
// to cache it here for further user reads
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), meta->fd,
|
||||
nullptr /* range_del_agg */,
|
||||
sub_compact->compaction->mutable_cf_options()->prefix_extractor.get(),
|
||||
nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
false, nullptr /* arena */, false /* skip_filters */,
|
||||
compact_->compaction->output_level());
|
||||
s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
}
|
||||
s = iter->status();
|
||||
}
|
||||
|
||||
delete iter;
|
||||
|
||||
if (s.ok() && (current_entries > 0 || tp.num_range_deletions > 0)) {
|
||||
// Output to event logger and fire events.
|
||||
if (s.ok()) {
|
||||
tp = sub_compact->builder->GetTableProperties();
|
||||
sub_compact->current_output()->table_properties =
|
||||
std::make_shared<TableProperties>(tp);
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64
|
||||
" keys, %" PRIu64 " bytes%s",
|
||||
cfd->GetName().c_str(), job_id_, output_number,
|
||||
current_entries, current_bytes,
|
||||
meta->marked_for_compaction ? " (need compaction)" : "");
|
||||
}
|
||||
sub_compact->current_output()->table_properties =
|
||||
std::make_shared<TableProperties>(tp);
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64
|
||||
" keys, %" PRIu64 " bytes%s",
|
||||
cfd->GetName().c_str(), job_id_, output_number,
|
||||
current_entries, current_bytes,
|
||||
meta->marked_for_compaction ? " (need compaction)" : "");
|
||||
}
|
||||
std::string fname;
|
||||
FileDescriptor output_fd;
|
||||
@@ -1239,21 +1348,12 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
if (sfm->IsMaxAllowedSpaceReached()) {
|
||||
// TODO(ajkr): should we return OK() if max space was reached by the final
|
||||
// compaction output file (similarly to how flush works when full)?
|
||||
s = Status::NoSpace("Max allowed space was reached");
|
||||
s = Status::SpaceLimit("Max allowed space was reached");
|
||||
TEST_SYNC_POINT(
|
||||
"CompactionJob::FinishCompactionOutputFile:"
|
||||
"MaxAllowedSpaceReached");
|
||||
InstrumentedMutexLock l(db_mutex_);
|
||||
if (db_bg_error_->ok()) {
|
||||
Status new_bg_error = s;
|
||||
// may temporarily unlock and lock the mutex.
|
||||
EventHelpers::NotifyOnBackgroundError(
|
||||
cfd->ioptions()->listeners, BackgroundErrorReason::kCompaction,
|
||||
&new_bg_error, db_mutex_);
|
||||
if (!new_bg_error.ok()) {
|
||||
*db_bg_error_ = new_bg_error;
|
||||
}
|
||||
}
|
||||
db_error_handler_->SetBGError(s, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1392,7 +1492,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
cfd->internal_comparator(), cfd->int_tbl_prop_collector_factories(),
|
||||
cfd->GetID(), cfd->GetName(), sub_compact->outfile.get(),
|
||||
sub_compact->compaction->output_compression(),
|
||||
cfd->ioptions()->compression_opts,
|
||||
sub_compact->compaction->output_compression_opts(),
|
||||
sub_compact->compaction->output_level(), &sub_compact->compression_dict,
|
||||
skip_filters, output_file_creation_time));
|
||||
LogFlush(db_options_.info_log);
|
||||
|
||||
+3
-2
@@ -47,6 +47,7 @@
|
||||
namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class ErrorHandler;
|
||||
class MemTable;
|
||||
class SnapshotChecker;
|
||||
class TableCache;
|
||||
@@ -64,7 +65,7 @@ class CompactionJob {
|
||||
LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory,
|
||||
Statistics* stats, InstrumentedMutex* db_mutex,
|
||||
Status* db_bg_error,
|
||||
ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
@@ -145,7 +146,7 @@ class CompactionJob {
|
||||
Directory* output_directory_;
|
||||
Statistics* stats_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
Status* db_bg_error_;
|
||||
ErrorHandler* db_error_handler_;
|
||||
// If there were two snapshots with seq numbers s1 and
|
||||
// s2 and s1 < s2, and if we find two instances of a key k1 then lies
|
||||
// entirely within s1 and s2, then the earlier version of k1 can be safely
|
||||
|
||||
@@ -98,7 +98,7 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
CompactionJobStatsTest() : env_(Env::Default()) {
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
dbname_ = test::TmpDir(env_) + "/compaction_job_stats_test";
|
||||
dbname_ = test::PerThreadDBPath("compaction_job_stats_test");
|
||||
alternative_wal_dir_ = dbname_ + "/wal";
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
|
||||
+13
-11
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/version_set.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -67,7 +68,7 @@ class CompactionJobTest : public testing::Test {
|
||||
public:
|
||||
CompactionJobTest()
|
||||
: env_(Env::Default()),
|
||||
dbname_(test::TmpDir() + "/compaction_job_test"),
|
||||
dbname_(test::PerThreadDBPath("compaction_job_test")),
|
||||
db_options_(),
|
||||
mutable_cf_options_(cf_options_),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
@@ -77,7 +78,8 @@ class CompactionJobTest : public testing::Test {
|
||||
&write_controller_)),
|
||||
shutting_down_(false),
|
||||
preserve_deletes_seqnum_(0),
|
||||
mock_table_factory_(new mock::MockTableFactory()) {
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
error_handler_(db_options_, &mutex_) {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
@@ -246,7 +248,8 @@ class CompactionJobTest : public testing::Test {
|
||||
Compaction compaction(cfd->current()->storage_info(), *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
compaction_input_files, 1, 1024 * 1024,
|
||||
10 * 1024 * 1024, 0, kNoCompression, 0, {}, true);
|
||||
10 * 1024 * 1024, 0, kNoCompression,
|
||||
cfd->ioptions()->compression_opts, 0, {}, true);
|
||||
compaction.SetInputVersion(cfd->current());
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
@@ -254,13 +257,12 @@ class CompactionJobTest : public testing::Test {
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
// TODO(yiwu) add a mock snapshot checker and add test for it.
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
CompactionJob compaction_job(0, &compaction, db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_, &log_buffer,
|
||||
nullptr, nullptr, nullptr, &mutex_, &bg_error_,
|
||||
snapshots, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, table_cache_, &event_logger,
|
||||
false, false, dbname_, &compaction_job_stats_);
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, env_options_, versions_.get(),
|
||||
&shutting_down_, preserve_deletes_seqnum_, &log_buffer, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
compaction_job.Prepare();
|
||||
@@ -302,7 +304,7 @@ class CompactionJobTest : public testing::Test {
|
||||
ColumnFamilyData* cfd_;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_;
|
||||
std::shared_ptr<MergeOperator> merge_op_;
|
||||
Status bg_error_;
|
||||
ErrorHandler error_handler_;
|
||||
};
|
||||
|
||||
TEST_F(CompactionJobTest, Simple) {
|
||||
|
||||
+44
-16
@@ -110,6 +110,24 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
}
|
||||
}
|
||||
|
||||
CompressionOptions GetCompressionOptions(const ImmutableCFOptions& ioptions,
|
||||
const VersionStorageInfo* vstorage,
|
||||
int level,
|
||||
const bool enable_compression) {
|
||||
if (!enable_compression) {
|
||||
return ioptions.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 &&
|
||||
level >= (vstorage->num_non_empty_levels() - 1) &&
|
||||
ioptions.bottommost_compression_opts.enabled) {
|
||||
return ioptions.bottommost_compression_opts;
|
||||
}
|
||||
return ioptions.compression_opts;
|
||||
}
|
||||
|
||||
CompactionPicker::CompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: ioptions_(ioptions), icmp_(icmp) {}
|
||||
@@ -301,7 +319,7 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
new Compaction(vstorage, ioptions_, mutable_cf_options, input_files,
|
||||
output_level, compact_options.output_file_size_limit,
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id,
|
||||
compact_options.compression,
|
||||
compact_options.compression, ioptions_.compression_opts,
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, true);
|
||||
RegisterCompaction(c);
|
||||
@@ -569,11 +587,13 @@ Compaction* CompactionPicker::CompactRange(
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs),
|
||||
output_level, MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
ioptions_.compaction_style),
|
||||
output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
ioptions_.compaction_style),
|
||||
/* max_compaction_bytes */ LLONG_MAX, output_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
max_subcompactions, /* grandparents */ {}, /* is manual */ true);
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
@@ -679,12 +699,13 @@ Compaction* CompactionPicker::CompactRange(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(compaction_inputs),
|
||||
output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
ioptions_.compaction_style, vstorage->base_level(),
|
||||
ioptions_.level_compaction_dynamic_level_bytes),
|
||||
ioptions_.compaction_style, vstorage->base_level(),
|
||||
ioptions_.level_compaction_dynamic_level_bytes),
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
/* max_subcompactions */ 0, std::move(grandparents),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
max_subcompactions, std::move(grandparents),
|
||||
/* is manual compaction */ true);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
@@ -853,6 +874,11 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (RangeOverlapWithCompaction(smallestkey, largestkey, output_level)) {
|
||||
return Status::Aborted(
|
||||
"A running compaction is writing to the same output level in an "
|
||||
"overlapping key range");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -1324,12 +1350,13 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
vstorage_, ioptions_, mutable_cf_options_, std::move(compaction_inputs_),
|
||||
output_level_,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level_,
|
||||
ioptions_.compaction_style, vstorage_->base_level(),
|
||||
ioptions_.level_compaction_dynamic_level_bytes),
|
||||
ioptions_.compaction_style, vstorage_->base_level(),
|
||||
ioptions_.level_compaction_dynamic_level_bytes),
|
||||
mutable_cf_options_.max_compaction_bytes,
|
||||
GetPathId(ioptions_, mutable_cf_options_, output_level_),
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level_, vstorage_->base_level()),
|
||||
GetCompressionOptions(ioptions_, vstorage_, output_level_),
|
||||
/* max_subcompactions */ 0, std::move(grandparents_), is_manual_,
|
||||
start_level_score_, false /* deletion_compaction */, compaction_reason_);
|
||||
|
||||
@@ -1579,9 +1606,9 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
|
||||
kNoCompression, /* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0), /* is deletion compaction */ true,
|
||||
CompactionReason::kFIFOTtl);
|
||||
kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0,
|
||||
{}, /* is manual */ false, vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOTtl);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -1619,8 +1646,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
16 * 1024 * 1024 /* output file size limit */,
|
||||
0 /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID */, mutable_cf_options.compression,
|
||||
0 /* max_subcompactions */, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0), /* is deletion compaction */ false,
|
||||
ioptions_.compression_opts, 0 /* max_subcompactions */, {},
|
||||
/* is manual */ false, vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false,
|
||||
CompactionReason::kFIFOReduceNumFiles);
|
||||
return c;
|
||||
}
|
||||
@@ -1666,9 +1694,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), 0, 0, 0, 0,
|
||||
kNoCompression, /* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0), /* is deletion compaction */ true,
|
||||
CompactionReason::kFIFOMaxSize);
|
||||
kNoCompression, ioptions_.compression_opts, /* max_subcompactions */ 0,
|
||||
{}, /* is manual */ false, vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOMaxSize);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
@@ -313,4 +313,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);
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -636,6 +636,8 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns(
|
||||
LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, start_level,
|
||||
1, enable_compression),
|
||||
GetCompressionOptions(ioptions_, vstorage, start_level,
|
||||
enable_compression),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score, false /* deletion_compaction */, compaction_reason);
|
||||
}
|
||||
@@ -659,6 +661,10 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSizeAmp(
|
||||
size_t start_index = 0;
|
||||
const SortedRun* sr = nullptr;
|
||||
|
||||
if (sorted_runs.back().being_compacted) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Skip files that are already being compacted
|
||||
for (size_t loop = 0; loop < sorted_runs.size() - 1; loop++) {
|
||||
sr = &sorted_runs[loop];
|
||||
@@ -765,12 +771,13 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSizeAmp(
|
||||
}
|
||||
|
||||
return new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs),
|
||||
output_level, MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
1),
|
||||
GetCompressionOptions(ioptions_, vstorage, output_level),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score, false /* deletion_compaction */,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
@@ -890,6 +897,7 @@ Compaction* UniversalCompactionPicker::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,
|
||||
score, false /* deletion_compaction */,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
|
||||
+92
-13
@@ -251,7 +251,9 @@ class TwoStrComparator : public Comparator {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
class ComparatorDBTest : public testing::Test {
|
||||
class ComparatorDBTest
|
||||
: public testing::Test,
|
||||
virtual public ::testing::WithParamInterface<uint32_t> {
|
||||
private:
|
||||
std::string dbname_;
|
||||
Env* env_;
|
||||
@@ -262,7 +264,11 @@ class ComparatorDBTest : public testing::Test {
|
||||
public:
|
||||
ComparatorDBTest() : env_(Env::Default()), db_(nullptr) {
|
||||
comparator = BytewiseComparator();
|
||||
dbname_ = test::TmpDir() + "/comparator_db_test";
|
||||
dbname_ = test::PerThreadDBPath("comparator_db_test");
|
||||
BlockBasedTableOptions toptions;
|
||||
toptions.format_version = GetParam();
|
||||
last_options_.table_factory.reset(
|
||||
rocksdb::NewBlockBasedTableFactory(toptions));
|
||||
EXPECT_OK(DestroyDB(dbname_, last_options_));
|
||||
}
|
||||
|
||||
@@ -274,8 +280,12 @@ class ComparatorDBTest : public testing::Test {
|
||||
|
||||
DB* GetDB() { return db_; }
|
||||
|
||||
void SetOwnedComparator(const Comparator* cmp) {
|
||||
comparator_guard.reset(cmp);
|
||||
void SetOwnedComparator(const Comparator* cmp, bool owner = true) {
|
||||
if (owner) {
|
||||
comparator_guard.reset(cmp);
|
||||
} else {
|
||||
comparator_guard.reset();
|
||||
}
|
||||
comparator = cmp;
|
||||
last_options_.comparator = cmp;
|
||||
}
|
||||
@@ -304,7 +314,12 @@ class ComparatorDBTest : public testing::Test {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ComparatorDBTest, Bytewise) {
|
||||
INSTANTIATE_TEST_CASE_P(FormatDef, ComparatorDBTest,
|
||||
testing::Values(test::kDefaultFormatVersion));
|
||||
INSTANTIATE_TEST_CASE_P(FormatLatest, ComparatorDBTest,
|
||||
testing::Values(test::kLatestFormatVersion));
|
||||
|
||||
TEST_P(ComparatorDBTest, Bytewise) {
|
||||
for (int rand_seed = 301; rand_seed < 306; rand_seed++) {
|
||||
DestroyAndReopen();
|
||||
Random rnd(rand_seed);
|
||||
@@ -314,7 +329,7 @@ TEST_F(ComparatorDBTest, Bytewise) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComparatorDBTest, SimpleSuffixReverseComparator) {
|
||||
TEST_P(ComparatorDBTest, SimpleSuffixReverseComparator) {
|
||||
SetOwnedComparator(new test::SimpleSuffixReverseComparator());
|
||||
|
||||
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) {
|
||||
@@ -340,8 +355,8 @@ TEST_F(ComparatorDBTest, SimpleSuffixReverseComparator) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComparatorDBTest, Uint64Comparator) {
|
||||
SetOwnedComparator(test::Uint64Comparator());
|
||||
TEST_P(ComparatorDBTest, Uint64Comparator) {
|
||||
SetOwnedComparator(test::Uint64Comparator(), false /* owner */);
|
||||
|
||||
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) {
|
||||
Options* opt = GetOptions();
|
||||
@@ -364,7 +379,7 @@ TEST_F(ComparatorDBTest, Uint64Comparator) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComparatorDBTest, DoubleComparator) {
|
||||
TEST_P(ComparatorDBTest, DoubleComparator) {
|
||||
SetOwnedComparator(new DoubleComparator());
|
||||
|
||||
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) {
|
||||
@@ -389,7 +404,7 @@ TEST_F(ComparatorDBTest, DoubleComparator) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComparatorDBTest, HashComparator) {
|
||||
TEST_P(ComparatorDBTest, HashComparator) {
|
||||
SetOwnedComparator(new HashComparator());
|
||||
|
||||
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) {
|
||||
@@ -408,7 +423,7 @@ TEST_F(ComparatorDBTest, HashComparator) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComparatorDBTest, TwoStrComparator) {
|
||||
TEST_P(ComparatorDBTest, TwoStrComparator) {
|
||||
SetOwnedComparator(new TwoStrComparator());
|
||||
|
||||
for (int rnd_seed = 301; rnd_seed < 316; rnd_seed++) {
|
||||
@@ -434,7 +449,71 @@ TEST_F(ComparatorDBTest, TwoStrComparator) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ComparatorDBTest, FindShortestSeparator) {
|
||||
TEST_P(ComparatorDBTest, IsSameLengthImmediateSuccessor) {
|
||||
{
|
||||
// different length
|
||||
Slice s("abcxy");
|
||||
Slice t("abcxyz");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
Slice s("abcxyz");
|
||||
Slice t("abcxy");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
// not last byte different
|
||||
Slice s("abc1xyz");
|
||||
Slice t("abc2xyz");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
// same string
|
||||
Slice s("abcxyz");
|
||||
Slice t("abcxyz");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
Slice s("abcxy");
|
||||
Slice t("abcxz");
|
||||
ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
Slice s("abcxz");
|
||||
Slice t("abcxy");
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
const char s_array[] = "\x50\x8a\xac";
|
||||
const char t_array[] = "\x50\x8a\xad";
|
||||
Slice s(s_array);
|
||||
Slice t(t_array);
|
||||
ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
const char s_array[] = "\x50\x8a\xff";
|
||||
const char t_array[] = "\x50\x8b\x00";
|
||||
Slice s(s_array, 3);
|
||||
Slice t(t_array, 3);
|
||||
ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
const char s_array[] = "\x50\x8a\xff\xff";
|
||||
const char t_array[] = "\x50\x8b\x00\x00";
|
||||
Slice s(s_array, 4);
|
||||
Slice t(t_array, 4);
|
||||
ASSERT_TRUE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
{
|
||||
const char s_array[] = "\x50\x8a\xff\xff";
|
||||
const char t_array[] = "\x50\x8b\x00\x01";
|
||||
Slice s(s_array, 4);
|
||||
Slice t(t_array, 4);
|
||||
ASSERT_FALSE(BytewiseComparator()->IsSameLengthImmediateSuccessor(s, t));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ComparatorDBTest, FindShortestSeparator) {
|
||||
std::string s1 = "abc1xyz";
|
||||
std::string s2 = "abc3xy";
|
||||
|
||||
@@ -468,7 +547,7 @@ TEST_F(ComparatorDBTest, FindShortestSeparator) {
|
||||
ASSERT_TRUE(s1 > s2);
|
||||
}
|
||||
|
||||
TEST_F(ComparatorDBTest, SeparatorSuccessorRandomizeTest) {
|
||||
TEST_P(ComparatorDBTest, SeparatorSuccessorRandomizeTest) {
|
||||
// Char list for boundary cases.
|
||||
std::array<unsigned char, 6> char_list{{0, 1, 2, 253, 254, 255}};
|
||||
Random rnd(301);
|
||||
|
||||
+2
-1
@@ -49,10 +49,11 @@ Status VerifySstFileChecksum(const Options& options,
|
||||
unique_ptr<TableReader> table_reader;
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(std::move(file), file_path));
|
||||
const bool kImmortal = true;
|
||||
s = ioptions.table_factory->NewTableReader(
|
||||
TableReaderOptions(ioptions, options.prefix_extractor.get(), env_options,
|
||||
internal_comparator, false /* skip_filters */,
|
||||
-1 /* level */),
|
||||
!kImmortal, -1 /* level */),
|
||||
std::move(file_reader), file_size, &table_reader,
|
||||
false /* prefetch_index_and_filter_in_cache */);
|
||||
if (!s.ok()) {
|
||||
|
||||
@@ -48,7 +48,7 @@ class CorruptionTest : public testing::Test {
|
||||
tiny_cache_ = NewLRUCache(100, 4);
|
||||
options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options_.env = &env_;
|
||||
dbname_ = test::TmpDir() + "/corruption_test";
|
||||
dbname_ = test::PerThreadDBPath("corruption_test");
|
||||
DestroyDB(dbname_, options_);
|
||||
|
||||
db_ = nullptr;
|
||||
|
||||
@@ -25,7 +25,7 @@ class CuckooTableDBTest : public testing::Test {
|
||||
|
||||
public:
|
||||
CuckooTableDBTest() : env_(Env::Default()) {
|
||||
dbname_ = test::TmpDir() + "/cuckoo_table_db_test";
|
||||
dbname_ = test::PerThreadDBPath("cuckoo_table_db_test");
|
||||
EXPECT_OK(DestroyDB(dbname_, Options()));
|
||||
db_ = nullptr;
|
||||
Reopen();
|
||||
|
||||
+1
-1
@@ -892,7 +892,7 @@ class TestEnv : public EnvWrapper {
|
||||
|
||||
TEST_F(DBBasicTest, DBClose) {
|
||||
Options options = GetDefaultOptions();
|
||||
std::string dbname = test::TmpDir(env_) + "/db_close_test";
|
||||
std::string dbname = test::PerThreadDBPath("db_close_test");
|
||||
ASSERT_OK(DestroyDB(dbname, options));
|
||||
|
||||
DB* db = nullptr;
|
||||
|
||||
@@ -39,6 +39,26 @@ class DBBloomFilterTestWithParam
|
||||
}
|
||||
};
|
||||
|
||||
class SliceTransformLimitedDomainGeneric : public SliceTransform {
|
||||
const char* Name() const override {
|
||||
return "SliceTransformLimitedDomainGeneric";
|
||||
}
|
||||
|
||||
Slice Transform(const Slice& src) const override {
|
||||
return Slice(src.data(), 5);
|
||||
}
|
||||
|
||||
bool InDomain(const Slice& src) const override {
|
||||
// prefix will be x????
|
||||
return src.size() >= 5;
|
||||
}
|
||||
|
||||
bool InRange(const Slice& dst) const override {
|
||||
// prefix will be x????
|
||||
return dst.size() == 5;
|
||||
}
|
||||
};
|
||||
|
||||
// KeyMayExist can lead to a few false positives, but not false negatives.
|
||||
// To make test deterministic, use a much larger number of bits per key-20 than
|
||||
// bits in the key, so that false positives are eliminated
|
||||
@@ -117,6 +137,53 @@ TEST_P(DBBloomFilterTestWithParam, KeyMayExist) {
|
||||
ChangeOptions(kSkipPlainTable | kSkipHashIndex | kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, GetFilterByPrefixBloomCustomPrefixExtractor) {
|
||||
for (bool partition_filters : {true, false}) {
|
||||
Options options = last_options_;
|
||||
options.prefix_extractor =
|
||||
std::make_shared<SliceTransformLimitedDomainGeneric>();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
if (partition_filters) {
|
||||
bbto.partition_filters = true;
|
||||
bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
}
|
||||
bbto.whole_key_filtering = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions wo;
|
||||
ReadOptions ro;
|
||||
FlushOptions fo;
|
||||
fo.wait = true;
|
||||
std::string value;
|
||||
|
||||
ASSERT_OK(dbfull()->Put(wo, "barbarbar", "foo"));
|
||||
ASSERT_OK(dbfull()->Put(wo, "barbarbar2", "foo2"));
|
||||
ASSERT_OK(dbfull()->Put(wo, "foofoofoo", "bar"));
|
||||
|
||||
dbfull()->Flush(fo);
|
||||
|
||||
ASSERT_EQ("foo", Get("barbarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("foo2", Get("barbarbar2"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
ASSERT_EQ("NOT_FOUND", Get("barbarbar3"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("barfoofoo"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 1);
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("foobarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
|
||||
|
||||
ro.total_order_seek = true;
|
||||
ASSERT_TRUE(db_->Get(ro, "foobarbar", &value).IsNotFound());
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBloomFilterTest, GetFilterByPrefixBloom) {
|
||||
for (bool partition_filters : {true, false}) {
|
||||
Options options = last_options_;
|
||||
@@ -1097,6 +1164,413 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) {
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
}
|
||||
|
||||
int CountIter(std::unique_ptr<Iterator>& iter, const Slice& key) {
|
||||
int count = 0;
|
||||
for (iter->Seek(key); iter->Valid() && iter->status() == Status::OK();
|
||||
iter->Next()) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// use iterate_upper_bound to hint compatiability of existing bloom filters.
|
||||
// The BF is considered compatible if 1) upper bound and seek key transform
|
||||
// into the same string, or 2) the transformed seek key is of the same length
|
||||
// as the upper bound and two keys are adjacent according to the comparator.
|
||||
TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
int iteration = 0;
|
||||
for (bool use_block_based_builder : {true, false}) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(4));
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
// Enable prefix bloom for SST files
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(10, use_block_based_builder));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("abcdxxx0", "val1"));
|
||||
ASSERT_OK(Put("abcdxxx1", "val2"));
|
||||
ASSERT_OK(Put("abcdxxx2", "val3"));
|
||||
ASSERT_OK(Put("abcdxxx3", "val4"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
{
|
||||
// prefix_extractor has not changed, BF will always be read
|
||||
Slice upper_bound("abce");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcd0000"), 4);
|
||||
}
|
||||
{
|
||||
Slice upper_bound("abcdzzzz");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcd0000"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:5"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.5"));
|
||||
{
|
||||
// BF changed, [abcdxx00, abce) is a valid bound, will trigger BF read
|
||||
Slice upper_bound("abce");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx00"), 4);
|
||||
// should check bloom filter since upper bound meets requirement
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + iteration);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
{
|
||||
// [abcdxx01, abcey) is not valid bound since upper bound is too long for
|
||||
// the BF in SST (capped:4)
|
||||
Slice upper_bound("abcey");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx01"), 4);
|
||||
// should skip bloom filter since upper bound is too long
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + iteration);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
{
|
||||
// [abcdxx02, abcdy) is a valid bound since the prefix is the same
|
||||
Slice upper_bound("abcdy");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abcdxx02"), 4);
|
||||
// should check bloom filter since upper bound matches transformed seek
|
||||
// key
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + iteration * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
{
|
||||
// [aaaaaaaa, abce) is not a valid bound since 1) they don't share the
|
||||
// same prefix, 2) the prefixes are not consecutive
|
||||
Slice upper_bound("abce");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "aaaaaaaa"), 0);
|
||||
// should skip bloom filter since mismatch is found
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + iteration * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:3"}}));
|
||||
{
|
||||
// [abc, abd) is not a valid bound since the upper bound is too short
|
||||
// for BF (capped:4)
|
||||
Slice upper_bound("abd");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + iteration * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:4"}}));
|
||||
{
|
||||
// set back to capped:4 and verify BF is always read
|
||||
Slice upper_bound("abd");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "abc"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
3 + iteration * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
}
|
||||
iteration++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create multiple SST files each with a different prefix_extractor config,
|
||||
// verify iterators can read all SST files using the latest config.
|
||||
TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
int iteration = 0;
|
||||
for (bool use_block_based_builder : {true, false}) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
// Enable prefix bloom for SST files
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(10, use_block_based_builder));
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Slice upper_bound("foz90000");
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
|
||||
// first SST with fixed:1 BF
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foq1", "bar1"));
|
||||
ASSERT_OK(Put("fpa", "0"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
std::unique_ptr<Iterator> iter_old(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 1);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
1 + iteration);
|
||||
ASSERT_EQ(CountIter(iter, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
1 + iteration);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
|
||||
// second SST with capped:3 BF
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Put("foq5", "bar5"));
|
||||
ASSERT_OK(Put("fpb", "1"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
{
|
||||
// BF is cappped:3 now
|
||||
std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_tmp, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
2 + iteration * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0);
|
||||
// both counters are incremented because BF is "not changed" for 1 of the
|
||||
// 2 SST files, so filter is checked once and found no match.
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
3 + iteration * 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.2"));
|
||||
// third SST with fixed:2 BF
|
||||
ASSERT_OK(Put("foo6", "bar6"));
|
||||
ASSERT_OK(Put("foo7", "bar7"));
|
||||
ASSERT_OK(Put("foq8", "bar8"));
|
||||
ASSERT_OK(Put("fpc", "2"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
{
|
||||
// BF is fixed:2 now
|
||||
std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_tmp, "foo"), 9);
|
||||
// the first and last BF are checked
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
4 + iteration * 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 1);
|
||||
ASSERT_EQ(CountIter(iter_tmp, "gpk"), 0);
|
||||
// only last BF is checked and not found
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
5 + iteration * 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
|
||||
}
|
||||
|
||||
// iter_old can only see the first SST, so checked plus 1
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
6 + iteration * 3);
|
||||
// iter was created after the first setoptions call so only full filter
|
||||
// will check the filter
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
6 + iteration * 4);
|
||||
|
||||
{
|
||||
// keys in all three SSTs are visible to iterator
|
||||
// The range of [foo, foz90000] is compatible with (fixed:1) and (fixed:2)
|
||||
// so +2 for checked counter
|
||||
std::unique_ptr<Iterator> iter_all(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 9);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
7 + iteration * 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 2);
|
||||
ASSERT_EQ(CountIter(iter_all, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
8 + iteration * 5);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
{
|
||||
std::unique_ptr<Iterator> iter_all(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 6);
|
||||
// all three SST are checked because the current options has the same as
|
||||
// the remaining SST (capped:3)
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
9 + iteration * 7);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
ASSERT_EQ(CountIter(iter_all, "gpk"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED),
|
||||
10 + iteration * 7);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 4);
|
||||
}
|
||||
// TODO(Zhongyi): Maybe also need to add Get calls to test point look up?
|
||||
iteration++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new column family in a running DB, change prefix_extractor
|
||||
// dynamically, verify the iterator created on the new column family behaves
|
||||
// as expected
|
||||
TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
int iteration = 0;
|
||||
for (bool use_block_based_builder : {true, false}) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
// Enable prefix bloom for SST files
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(10, use_block_based_builder));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu" + std::to_string(iteration)}, options);
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
// create a new CF and set prefix_extractor dynamically
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(3));
|
||||
CreateColumnFamilies({"ramen_dojo_" + std::to_string(iteration)}, options);
|
||||
ASSERT_EQ(0,
|
||||
strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_OK(Put(2, "foo3", "bar3"));
|
||||
ASSERT_OK(Put(2, "foo4", "bar4"));
|
||||
ASSERT_OK(Put(2, "foo5", "bar5"));
|
||||
ASSERT_OK(Put(2, "foq6", "bar6"));
|
||||
ASSERT_OK(Put(2, "fpq7", "bar7"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(read_options, handles_[2]));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[2], {{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ(0,
|
||||
strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.2"));
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(read_options, handles_[2]));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 4);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[2]));
|
||||
dbfull()->DestroyColumnFamilyHandle(handles_[2]);
|
||||
handles_[2] = nullptr;
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
dbfull()->DestroyColumnFamilyHandle(handles_[1]);
|
||||
handles_[1] = nullptr;
|
||||
iteration++;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify it's possible to change prefix_extractor at runtime and iterators
|
||||
// behaves as expected
|
||||
TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
int iteration = 0;
|
||||
for (bool use_block_based_builder : {true, false}) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
// Enable prefix bloom for SST files
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(
|
||||
NewBloomFilterPolicy(10, use_block_based_builder));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("fpa", "0"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Put("foo5", "bar5"));
|
||||
ASSERT_OK(Put("fpb", "1"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(Put("foo6", "bar6"));
|
||||
ASSERT_OK(Put("foo7", "bar7"));
|
||||
ASSERT_OK(Put("foo8", "bar8"));
|
||||
ASSERT_OK(Put("fpc", "2"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 3);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
std::unique_ptr<Iterator> iter_old(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// "fp*" should be skipped
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 9);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 6);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
|
||||
// iterator created before should not be affected and see all keys
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 9);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
ASSERT_EQ(CountIter(iter_old, "abc"), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 12);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
iteration++;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -3704,6 +3704,68 @@ TEST_F(DBCompactionTest, CompactionStatsTest) {
|
||||
VerifyCompactionStats(*cfd, *collector);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactFilesOutputRangeConflict) {
|
||||
// LSM setup:
|
||||
// L1: [ba bz]
|
||||
// L2: [a b] [c d]
|
||||
// L3: [a b] [c d]
|
||||
//
|
||||
// Thread 1: Thread 2:
|
||||
// Begin compacting all L2->L3
|
||||
// Compact [ba bz] L1->L3
|
||||
// End compacting all L2->L3
|
||||
//
|
||||
// The compaction operation in thread 2 should be disallowed because the range
|
||||
// overlaps with the compaction in thread 1, which also covers that range in
|
||||
// L3.
|
||||
Options options = CurrentOptions();
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
Reopen(options);
|
||||
|
||||
for (int level = 3; level >= 2; --level) {
|
||||
ASSERT_OK(Put("a", "val"));
|
||||
ASSERT_OK(Put("b", "val"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("c", "val"));
|
||||
ASSERT_OK(Put("d", "val"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(level);
|
||||
}
|
||||
ASSERT_OK(Put("ba", "val"));
|
||||
ASSERT_OK(Put("bz", "val"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"CompactFilesImpl:0",
|
||||
"DBCompactionTest::CompactFilesOutputRangeConflict:Thread2Begin"},
|
||||
{"DBCompactionTest::CompactFilesOutputRangeConflict:Thread2End",
|
||||
"CompactFilesImpl:1"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
auto bg_thread = port::Thread([&]() {
|
||||
// Thread 1
|
||||
std::vector<std::string> filenames = collector->GetFlushedFiles();
|
||||
filenames.pop_back();
|
||||
ASSERT_OK(db_->CompactFiles(CompactionOptions(), filenames,
|
||||
3 /* output_level */));
|
||||
});
|
||||
|
||||
// Thread 2
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::CompactFilesOutputRangeConflict:Thread2Begin");
|
||||
std::string filename = collector->GetFlushedFiles().back();
|
||||
ASSERT_FALSE(
|
||||
db_->CompactFiles(CompactionOptions(), {filename}, 3 /* output_level */)
|
||||
.ok());
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::CompactFilesOutputRangeConflict:Thread2End");
|
||||
|
||||
bg_thread.join();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam,
|
||||
::testing::Values(std::make_tuple(1, true),
|
||||
std::make_tuple(1, false),
|
||||
|
||||
+84
-14
@@ -32,6 +32,7 @@
|
||||
#include "db/db_info_dumper.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/flush_job.h"
|
||||
@@ -138,7 +139,7 @@ int64_t kDefaultLowPriThrottledRate = 2 * 1024 * 1024;
|
||||
} // namespace
|
||||
|
||||
DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
const bool seq_per_batch)
|
||||
const bool seq_per_batch, const bool batch_per_txn)
|
||||
: env_(options.env),
|
||||
dbname_(dbname),
|
||||
own_info_log_(options.info_log == nullptr),
|
||||
@@ -199,6 +200,7 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
two_write_queues_(options.two_write_queues),
|
||||
manual_wal_flush_(options.manual_wal_flush),
|
||||
seq_per_batch_(seq_per_batch),
|
||||
batch_per_txn_(batch_per_txn),
|
||||
// last_sequencee_ is always maintained by the main queue that also writes
|
||||
// to the memtable. When two_write_queues_ is disabled last seq in
|
||||
// memtable is the same as last seq published to the readers. When it is
|
||||
@@ -215,7 +217,11 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
// as well.
|
||||
use_custom_gc_(seq_per_batch),
|
||||
preserve_deletes_(options.preserve_deletes),
|
||||
closed_(false) {
|
||||
closed_(false),
|
||||
error_handler_(immutable_db_options_, &mutex_) {
|
||||
// !batch_per_trx_ implies seq_per_batch_ because it is only unset for
|
||||
// WriteUnprepared, which should use seq_per_batch_.
|
||||
assert(batch_per_txn_ || seq_per_batch_);
|
||||
env_->GetAbsolutePath(dbname, &db_absolute_path_);
|
||||
|
||||
// Reserve ten files or so for other uses and give the rest to TableCache.
|
||||
@@ -244,6 +250,43 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
preserve_deletes_seqnum_.store(0);
|
||||
}
|
||||
|
||||
Status DBImpl::Resume() {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Resuming DB");
|
||||
|
||||
InstrumentedMutexLock db_mutex(&mutex_);
|
||||
|
||||
if (!error_handler_.IsDBStopped() && !error_handler_.IsBGWorkStopped()) {
|
||||
// Nothing to do
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status s = error_handler_.GetBGError();
|
||||
if (s.severity() > Status::Severity::kHardError) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"DB resume requested but failed due to Fatal/Unrecoverable error");
|
||||
return s;
|
||||
}
|
||||
|
||||
JobContext job_context(0);
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
error_handler_.ClearBGError();
|
||||
mutex_.Unlock();
|
||||
|
||||
job_context.manifest_file_number = 1;
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "Successfully resumed DB");
|
||||
mutex_.Lock();
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
// No need to check BGError again. If something happened, event listener would be
|
||||
// notified and the operation causing it would have failed
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Will lock the mutex_, will wait for completion if wait is true
|
||||
void DBImpl::CancelAllBackgroundWork(bool wait) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -1037,12 +1080,19 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
|
||||
SequenceNumber snapshot;
|
||||
if (read_options.snapshot != nullptr) {
|
||||
// Note: In WritePrepared txns this is not necessary but not harmful either.
|
||||
// Because prep_seq > snapshot => commit_seq > snapshot so if a snapshot is
|
||||
// specified we should be fine with skipping seq numbers that are greater
|
||||
// than that.
|
||||
// Note: In WritePrepared txns this is not necessary but not harmful
|
||||
// either. Because prep_seq > snapshot => commit_seq > snapshot so if
|
||||
// a snapshot is specified we should be fine with skipping seq numbers
|
||||
// that are greater than that.
|
||||
//
|
||||
// In WriteUnprepared, we cannot set snapshot in the lookup key because we
|
||||
// may skip uncommitted data that should be visible to the transaction for
|
||||
// reading own writes.
|
||||
snapshot =
|
||||
reinterpret_cast<const SnapshotImpl*>(read_options.snapshot)->number_;
|
||||
if (callback) {
|
||||
snapshot = std::max(snapshot, callback->MaxUnpreparedSequenceNumber());
|
||||
}
|
||||
} else {
|
||||
// Since we get and reference the super version before getting
|
||||
// the snapshot number, without a mutex protection, it is possible
|
||||
@@ -1050,10 +1100,10 @@ Status DBImpl::GetImpl(const ReadOptions& read_options,
|
||||
// data for this snapshot is available. But it will contain all
|
||||
// the data available in the super version we have, which is also
|
||||
// a valid snapshot to read from.
|
||||
// We shouldn't get snapshot before finding and referencing the
|
||||
// super versipon because a flush happening in between may compact
|
||||
// away data for the snapshot, but the snapshot is earlier than the
|
||||
// data overwriting it, so users may see wrong results.
|
||||
// We shouldn't get snapshot before finding and referencing the super
|
||||
// version because a flush happening in between may compact away data for
|
||||
// the snapshot, but the snapshot is earlier than the data overwriting it,
|
||||
// so users may see wrong results.
|
||||
snapshot = last_seq_same_as_publish_seq_
|
||||
? versions_->LastSequence()
|
||||
: versions_->LastPublishedSequence();
|
||||
@@ -1845,6 +1895,13 @@ bool DBImpl::GetProperty(ColumnFamilyHandle* column_family,
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return cfd->internal_stats()->GetStringProperty(*property_info, property,
|
||||
value);
|
||||
} else if (property_info->handle_string_dbimpl) {
|
||||
std::string tmp_value;
|
||||
bool ret_value = (this->*(property_info->handle_string_dbimpl))(&tmp_value);
|
||||
if (ret_value) {
|
||||
*value = tmp_value;
|
||||
}
|
||||
return ret_value;
|
||||
}
|
||||
// Shouldn't reach here since exactly one of handle_string and handle_int
|
||||
// should be non-nullptr.
|
||||
@@ -1911,6 +1968,16 @@ bool DBImpl::GetIntPropertyInternal(ColumnFamilyData* cfd,
|
||||
}
|
||||
}
|
||||
|
||||
bool DBImpl::GetPropertyHandleOptionsStatistics(std::string* value) {
|
||||
assert(value != nullptr);
|
||||
Statistics* statistics = immutable_db_options_.statistics.get();
|
||||
if (!statistics) {
|
||||
return false;
|
||||
}
|
||||
*value = statistics->ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status DBImpl::ResetStats() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -2351,7 +2418,8 @@ Status DBImpl::GetDbIdentity(std::string& identity) const {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
id_file_reader.reset(new SequentialFileReader(std::move(idfile)));
|
||||
id_file_reader.reset(
|
||||
new SequentialFileReader(std::move(idfile), idfilename));
|
||||
}
|
||||
|
||||
uint64_t file_size;
|
||||
@@ -2667,7 +2735,9 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name) {
|
||||
// Retry if the file name happen to conflict with an existing one.
|
||||
s = GetEnv()->RenameFile(file_name, options_file_name);
|
||||
|
||||
DeleteObsoleteOptionsFiles();
|
||||
if (0 == disable_delete_obsolete_files_) {
|
||||
DeleteObsoleteOptionsFiles();
|
||||
}
|
||||
return s;
|
||||
#else
|
||||
(void)file_name;
|
||||
@@ -2854,9 +2924,9 @@ Status DBImpl::IngestExternalFile(
|
||||
std::list<uint64_t>::iterator pending_output_elem;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (!bg_error_.ok()) {
|
||||
if (error_handler_.IsDBStopped()) {
|
||||
// Don't ingest files when there is a bg_error
|
||||
return bg_error_;
|
||||
return error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
// Make sure that bg cleanup wont delete the files that we are ingesting
|
||||
|
||||
+76
-24
@@ -23,6 +23,8 @@
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/external_sst_file_ingestion_job.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/internal_stats.h"
|
||||
@@ -70,9 +72,12 @@ struct MemTableInfo;
|
||||
class DBImpl : public DB {
|
||||
public:
|
||||
DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
const bool seq_per_batch = false);
|
||||
const bool seq_per_batch = false, const bool batch_per_txn = true);
|
||||
virtual ~DBImpl();
|
||||
|
||||
using DB::Resume;
|
||||
virtual Status Resume() override;
|
||||
|
||||
// Implementations of the DB interface
|
||||
using DB::Put;
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
@@ -556,25 +561,50 @@ class DBImpl : public DB {
|
||||
// these will then be passed to TransactionDB so that
|
||||
// locks can be reacquired before writing can resume.
|
||||
struct RecoveredTransaction {
|
||||
uint64_t log_number_;
|
||||
std::string name_;
|
||||
WriteBatch* batch_;
|
||||
// The seq number of the first key in the batch
|
||||
SequenceNumber seq_;
|
||||
// Number of sub-batched. A new sub-batch is created if we txn attempts to
|
||||
// inserts a duplicate key,seq to memtable. This is currently used in
|
||||
// WritePrparedTxn
|
||||
size_t batch_cnt_;
|
||||
bool unprepared_;
|
||||
|
||||
struct BatchInfo {
|
||||
uint64_t log_number_;
|
||||
// TODO(lth): For unprepared, the memory usage here can be big for
|
||||
// unprepared transactions. This is only useful for rollbacks, and we
|
||||
// can in theory just keep keyset for that.
|
||||
WriteBatch* batch_;
|
||||
// Number of sub-batches. A new sub-batch is created if txn attempts to
|
||||
// insert a duplicate key,seq to memtable. This is currently used in
|
||||
// WritePreparedTxn/WriteUnpreparedTxn.
|
||||
size_t batch_cnt_;
|
||||
};
|
||||
|
||||
// This maps the seq of the first key in the batch to BatchInfo, which
|
||||
// contains WriteBatch and other information relevant to the batch.
|
||||
//
|
||||
// For WriteUnprepared, batches_ can have size greater than 1, but for
|
||||
// other write policies, it must be of size 1.
|
||||
std::map<SequenceNumber, BatchInfo> batches_;
|
||||
|
||||
explicit RecoveredTransaction(const uint64_t log, const std::string& name,
|
||||
WriteBatch* batch, SequenceNumber seq,
|
||||
size_t batch_cnt)
|
||||
: log_number_(log),
|
||||
name_(name),
|
||||
batch_(batch),
|
||||
seq_(seq),
|
||||
batch_cnt_(batch_cnt) {}
|
||||
size_t batch_cnt, bool unprepared)
|
||||
: name_(name), unprepared_(unprepared) {
|
||||
batches_[seq] = {log, batch, batch_cnt};
|
||||
}
|
||||
|
||||
~RecoveredTransaction() { delete batch_; }
|
||||
~RecoveredTransaction() {
|
||||
for (auto& it : batches_) {
|
||||
delete it.second.batch_;
|
||||
}
|
||||
}
|
||||
|
||||
void AddBatch(SequenceNumber seq, uint64_t log_number, WriteBatch* batch,
|
||||
size_t batch_cnt, bool unprepared) {
|
||||
assert(batches_.count(seq) == 0);
|
||||
batches_[seq] = {log_number, batch, batch_cnt};
|
||||
// Prior state must be unprepared, since the prepare batch must be the
|
||||
// last batch.
|
||||
assert(unprepared_);
|
||||
unprepared_ = unprepared;
|
||||
}
|
||||
};
|
||||
|
||||
bool allow_2pc() const { return immutable_db_options_.allow_2pc; }
|
||||
@@ -595,9 +625,19 @@ class DBImpl : public DB {
|
||||
|
||||
void InsertRecoveredTransaction(const uint64_t log, const std::string& name,
|
||||
WriteBatch* batch, SequenceNumber seq,
|
||||
size_t batch_cnt) {
|
||||
recovered_transactions_[name] =
|
||||
new RecoveredTransaction(log, name, batch, seq, batch_cnt);
|
||||
size_t batch_cnt, bool unprepared_batch) {
|
||||
// For WriteUnpreparedTxn, InsertRecoveredTransaction is called multiple
|
||||
// times for every unprepared batch encountered during recovery.
|
||||
//
|
||||
// If the transaction is prepared, then the last call to
|
||||
// InsertRecoveredTransaction will have unprepared_batch = false.
|
||||
auto rtxn = recovered_transactions_.find(name);
|
||||
if (rtxn == recovered_transactions_.end()) {
|
||||
recovered_transactions_[name] = new RecoveredTransaction(
|
||||
log, name, batch, seq, batch_cnt, unprepared_batch);
|
||||
} else {
|
||||
rtxn->second->AddBatch(seq, log, batch, batch_cnt, unprepared_batch);
|
||||
}
|
||||
logs_with_prep_tracker_.MarkLogAsContainingPrepSection(log);
|
||||
}
|
||||
|
||||
@@ -606,7 +646,10 @@ class DBImpl : public DB {
|
||||
assert(it != recovered_transactions_.end());
|
||||
auto* trx = it->second;
|
||||
recovered_transactions_.erase(it);
|
||||
logs_with_prep_tracker_.MarkLogAsHavingPrepSectionFlushed(trx->log_number_);
|
||||
for (const auto& info : trx->batches_) {
|
||||
logs_with_prep_tracker_.MarkLogAsHavingPrepSectionFlushed(
|
||||
info.second.log_number_);
|
||||
}
|
||||
delete trx;
|
||||
}
|
||||
|
||||
@@ -635,7 +678,7 @@ class DBImpl : public DB {
|
||||
static Status Open(const DBOptions& db_options, const std::string& name,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
const bool seq_per_batch);
|
||||
const bool seq_per_batch, const bool batch_per_txn);
|
||||
|
||||
virtual Status Close() override;
|
||||
|
||||
@@ -746,6 +789,7 @@ class DBImpl : public DB {
|
||||
friend class WritePreparedTxn;
|
||||
friend class WritePreparedTxnDB;
|
||||
friend class WriteBatchWithIndex;
|
||||
friend class WriteUnpreparedTxnDB;
|
||||
#ifndef ROCKSDB_LITE
|
||||
friend class ForwardIterator;
|
||||
#endif
|
||||
@@ -757,6 +801,7 @@ class DBImpl : public DB {
|
||||
friend class WriteCallbackTest_WriteWithCallbackTest_Test;
|
||||
friend class XFTransactionWriteHandler;
|
||||
friend class DBBlobIndexTest;
|
||||
friend class WriteUnpreparedTransactionTest_RecoveryRollbackUnprepared_Test;
|
||||
#endif
|
||||
struct CompactionState;
|
||||
|
||||
@@ -1265,9 +1310,6 @@ class DBImpl : public DB {
|
||||
PrepickedCompaction* prepicked_compaction;
|
||||
};
|
||||
|
||||
// Have we encountered a background error in paranoid mode?
|
||||
Status bg_error_;
|
||||
|
||||
// shall we disable deletion of obsolete files
|
||||
// if 0 the deletion is enabled.
|
||||
// if non-zero, files will not be getting deleted
|
||||
@@ -1379,6 +1421,7 @@ class DBImpl : public DB {
|
||||
bool GetIntPropertyInternal(ColumnFamilyData* cfd,
|
||||
const DBPropertyInfo& property_info,
|
||||
bool is_locked, uint64_t* value);
|
||||
bool GetPropertyHandleOptionsStatistics(std::string* value);
|
||||
|
||||
bool HasPendingManualCompaction();
|
||||
bool HasExclusiveManualCompaction();
|
||||
@@ -1407,6 +1450,13 @@ class DBImpl : public DB {
|
||||
//
|
||||
// Default: false
|
||||
const bool seq_per_batch_;
|
||||
// This determines during recovery whether we expect one writebatch per
|
||||
// recovered transaction, or potentially multiple writebatches per
|
||||
// transaction. For WriteUnprepared, this is set to false, since multiple
|
||||
// batches can exist per transaction.
|
||||
//
|
||||
// Default: true
|
||||
const bool batch_per_txn_;
|
||||
// LastSequence also indicates last published sequence visibile to the
|
||||
// readers. Otherwise LastPublishedSequence should be used.
|
||||
const bool last_seq_same_as_publish_seq_;
|
||||
@@ -1424,6 +1474,8 @@ class DBImpl : public DB {
|
||||
|
||||
// Flag to check whether Close() has been called on this DB
|
||||
bool closed_;
|
||||
|
||||
ErrorHandler error_handler_;
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db,
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
@@ -93,14 +94,7 @@ Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
// "number < current_log_number".
|
||||
MarkLogsSynced(current_log_number - 1, true, s);
|
||||
if (!s.ok()) {
|
||||
Status new_bg_error = s;
|
||||
// may temporarily unlock and lock the mutex.
|
||||
EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners,
|
||||
BackgroundErrorReason::kFlush,
|
||||
&new_bg_error, &mutex_);
|
||||
if (!new_bg_error.ok()) {
|
||||
bg_error_ = new_bg_error;
|
||||
}
|
||||
error_handler_.SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Failed");
|
||||
return s;
|
||||
}
|
||||
@@ -177,18 +171,9 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
cfd->current()->storage_info()->LevelSummary(&tmp));
|
||||
}
|
||||
|
||||
if (!s.ok() && !s.IsShutdownInProgress() &&
|
||||
immutable_db_options_.paranoid_checks && bg_error_.ok()) {
|
||||
if (!s.ok() && !s.IsShutdownInProgress()) {
|
||||
Status new_bg_error = s;
|
||||
// may temporarily unlock and lock the mutex.
|
||||
EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners,
|
||||
BackgroundErrorReason::kFlush,
|
||||
&new_bg_error, &mutex_);
|
||||
if (!new_bg_error.ok()) {
|
||||
// if a bad error happened (not ShutdownInProgress), paranoid_checks is
|
||||
// true, and the error isn't handled by callback, mark DB read-only
|
||||
bg_error_ = new_bg_error;
|
||||
}
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
if (s.ok()) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -202,18 +187,12 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
std::string file_path = MakeTableFileName(
|
||||
cfd->ioptions()->cf_paths[0].path, file_meta.fd.GetNumber());
|
||||
sfm->OnAddFile(file_path);
|
||||
if (sfm->IsMaxAllowedSpaceReached() && bg_error_.ok()) {
|
||||
Status new_bg_error = Status::NoSpace("Max allowed space was reached");
|
||||
if (sfm->IsMaxAllowedSpaceReached()) {
|
||||
Status new_bg_error = Status::SpaceLimit("Max allowed space was reached");
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached",
|
||||
&new_bg_error);
|
||||
// may temporarily unlock and lock the mutex.
|
||||
EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners,
|
||||
BackgroundErrorReason::kFlush,
|
||||
&new_bg_error, &mutex_);
|
||||
if (!new_bg_error.ok()) {
|
||||
bg_error_ = new_bg_error;
|
||||
}
|
||||
error_handler_.SetBGError(new_bg_error, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -674,7 +653,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()), stats_, &mutex_,
|
||||
&bg_error_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
&error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
@@ -736,16 +715,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
"[%s] [JOB %d] Compaction error: %s",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
job_context->job_id, status.ToString().c_str());
|
||||
if (immutable_db_options_.paranoid_checks && bg_error_.ok()) {
|
||||
Status new_bg_error = status;
|
||||
// may temporarily unlock and lock the mutex.
|
||||
EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners,
|
||||
BackgroundErrorReason::kCompaction,
|
||||
&new_bg_error, &mutex_);
|
||||
if (!new_bg_error.ok()) {
|
||||
bg_error_ = new_bg_error;
|
||||
}
|
||||
}
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
|
||||
if (output_file_names != nullptr) {
|
||||
@@ -1141,6 +1111,7 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
// Wait until the compaction completes
|
||||
s = WaitForFlushMemTable(cfd, &flush_memtable_id);
|
||||
}
|
||||
TEST_SYNC_POINT("FlushMemTableFinished");
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1149,7 +1120,7 @@ Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
Status s;
|
||||
// Wait until the compaction completes
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (cfd->imm()->NumNotFlushed() > 0 && bg_error_.ok() &&
|
||||
while (cfd->imm()->NumNotFlushed() > 0 && !error_handler_.IsDBStopped() &&
|
||||
(flush_memtable_id == nullptr ||
|
||||
cfd->imm()->GetEarliestMemTableID() <= *flush_memtable_id)) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
@@ -1163,8 +1134,8 @@ Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
}
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
if (!bg_error_.ok()) {
|
||||
s = bg_error_;
|
||||
if (error_handler_.IsDBStopped()) {
|
||||
s = error_handler_.GetBGError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -1383,9 +1354,13 @@ Status DBImpl::BackgroundFlush(bool* made_progress, JobContext* job_context,
|
||||
LogBuffer* log_buffer) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
Status status = bg_error_;
|
||||
if (status.ok() && shutting_down_.load(std::memory_order_acquire)) {
|
||||
status = Status::ShutdownInProgress();
|
||||
Status status;
|
||||
if (!error_handler_.IsBGWorkStopped()) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
status = Status::ShutdownInProgress();
|
||||
}
|
||||
} else {
|
||||
status = error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
@@ -1631,9 +1606,13 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
is_manual && manual_compaction->disallow_trivial_move;
|
||||
|
||||
CompactionJobStats compaction_job_stats;
|
||||
Status status = bg_error_;
|
||||
if (status.ok() && shutting_down_.load(std::memory_order_acquire)) {
|
||||
status = Status::ShutdownInProgress();
|
||||
Status status;
|
||||
if (!error_handler_.IsBGWorkStopped()) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
status = Status::ShutdownInProgress();
|
||||
}
|
||||
} else {
|
||||
status = error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
@@ -1905,7 +1884,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
|
||||
GetDataDir(c->column_family_data(), c->output_path_id()), stats_,
|
||||
&mutex_, &bg_error_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
&mutex_, &error_handler_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
@@ -1951,16 +1930,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log, "Compaction error: %s",
|
||||
status.ToString().c_str());
|
||||
if (immutable_db_options_.paranoid_checks && bg_error_.ok()) {
|
||||
Status new_bg_error = status;
|
||||
// may temporarily unlock and lock the mutex.
|
||||
EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners,
|
||||
BackgroundErrorReason::kCompaction,
|
||||
&new_bg_error, &mutex_);
|
||||
if (!new_bg_error.ok()) {
|
||||
bg_error_ = new_bg_error;
|
||||
}
|
||||
}
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
|
||||
if (is_manual) {
|
||||
|
||||
+3
-2
@@ -10,6 +10,7 @@
|
||||
#ifndef NDEBUG
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "monitoring/thread_status_updater.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -134,10 +135,10 @@ Status DBImpl::TEST_WaitForCompact(bool wait_unscheduled) {
|
||||
while ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_ ||
|
||||
(wait_unscheduled && unscheduled_compactions_)) &&
|
||||
bg_error_.ok()) {
|
||||
!error_handler_.IsDBStopped()) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
return bg_error_;
|
||||
return error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
void DBImpl::TEST_LockMutex() {
|
||||
|
||||
+33
-2
@@ -350,9 +350,32 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
std::vector<std::string> old_info_log_files;
|
||||
InfoLogPrefix info_log_prefix(!immutable_db_options_.db_log_dir.empty(),
|
||||
dbname_);
|
||||
|
||||
// File numbers of most recent two OPTIONS file in candidate_files (found in
|
||||
// previos FindObsoleteFiles(full_scan=true))
|
||||
// At this point, there must not be any duplicate file numbers in
|
||||
// candidate_files.
|
||||
uint64_t optsfile_num1 = std::numeric_limits<uint64_t>::min();
|
||||
uint64_t optsfile_num2 = std::numeric_limits<uint64_t>::min();
|
||||
for (const auto& candidate_file : candidate_files) {
|
||||
const std::string& fname = candidate_file.file_name;
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (!ParseFileName(fname, &number, info_log_prefix.prefix, &type) ||
|
||||
type != kOptionsFile) {
|
||||
continue;
|
||||
}
|
||||
if (number > optsfile_num1) {
|
||||
optsfile_num2 = optsfile_num1;
|
||||
optsfile_num1 = number;
|
||||
} else if (number > optsfile_num2) {
|
||||
optsfile_num2 = number;
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<uint64_t> files_to_del;
|
||||
for (const auto& candidate_file : candidate_files) {
|
||||
std::string to_delete = candidate_file.file_name;
|
||||
const std::string& to_delete = candidate_file.file_name;
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
// Ignore file if we cannot recognize it.
|
||||
@@ -401,11 +424,19 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
old_info_log_files.push_back(to_delete);
|
||||
}
|
||||
break;
|
||||
case kOptionsFile:
|
||||
keep = (number >= optsfile_num2);
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:1",
|
||||
reinterpret_cast<void*>(&number));
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:2",
|
||||
reinterpret_cast<void*>(&keep));
|
||||
break;
|
||||
case kCurrentFile:
|
||||
case kDBLockFile:
|
||||
case kIdentityFile:
|
||||
case kMetaDatabase:
|
||||
case kOptionsFile:
|
||||
case kBlobFile:
|
||||
keep = true;
|
||||
break;
|
||||
|
||||
+8
-6
@@ -14,6 +14,7 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "rocksdb/wal_filter.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
@@ -577,7 +578,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
continue;
|
||||
}
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file)));
|
||||
file_reader.reset(new SequentialFileReader(std::move(file), fname));
|
||||
}
|
||||
|
||||
// Create the log reader.
|
||||
@@ -719,7 +720,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
&batch, column_family_memtables_.get(), &flush_scheduler_, true,
|
||||
log_number, this, false /* concurrent_memtable_writes */,
|
||||
next_sequence, &has_valid_writes, seq_per_batch_);
|
||||
next_sequence, &has_valid_writes, seq_per_batch_, batch_per_txn_);
|
||||
MaybeIgnoreError(&status);
|
||||
if (!status.ok()) {
|
||||
// We are treating this as a failure while reading since we read valid
|
||||
@@ -1003,15 +1004,16 @@ Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) {
|
||||
Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr) {
|
||||
const bool seq_per_batch = true;
|
||||
const bool kSeqPerBatch = true;
|
||||
const bool kBatchPerTxn = true;
|
||||
return DBImpl::Open(db_options, dbname, column_families, handles, dbptr,
|
||||
!seq_per_batch);
|
||||
!kSeqPerBatch, kBatchPerTxn);
|
||||
}
|
||||
|
||||
Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
const bool seq_per_batch) {
|
||||
const bool seq_per_batch, const bool batch_per_txn) {
|
||||
Status s = SanitizeOptionsByTable(db_options, column_families);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -1031,7 +1033,7 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
std::max(max_write_buffer_size, cf.options.write_buffer_size);
|
||||
}
|
||||
|
||||
DBImpl* impl = new DBImpl(db_options, dbname, seq_per_batch);
|
||||
DBImpl* impl = new DBImpl(db_options, dbname, seq_per_batch, batch_per_txn);
|
||||
s = impl->env_->CreateDirIfMissing(impl->immutable_db_options_.wal_dir);
|
||||
if (s.ok()) {
|
||||
std::vector<std::string> paths;
|
||||
|
||||
+8
-23
@@ -12,6 +12,7 @@
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "options/options_helper.h"
|
||||
@@ -676,16 +677,7 @@ void DBImpl::WriteStatusCheck(const Status& status) {
|
||||
if (immutable_db_options_.paranoid_checks && !status.ok() &&
|
||||
!status.IsBusy() && !status.IsIncomplete()) {
|
||||
mutex_.Lock();
|
||||
if (bg_error_.ok()) {
|
||||
Status new_bg_error = status;
|
||||
// may temporarily unlock and lock the mutex.
|
||||
EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners,
|
||||
BackgroundErrorReason::kWriteCallback,
|
||||
&new_bg_error, &mutex_);
|
||||
if (!new_bg_error.ok()) {
|
||||
bg_error_ = new_bg_error; // stop compaction & fail any further writes
|
||||
}
|
||||
}
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kWriteCallback);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
}
|
||||
@@ -698,15 +690,8 @@ void DBImpl::MemTableInsertStatusCheck(const Status& status) {
|
||||
// ignore_missing_column_families.
|
||||
if (!status.ok()) {
|
||||
mutex_.Lock();
|
||||
assert(bg_error_.ok());
|
||||
Status new_bg_error = status;
|
||||
// may temporarily unlock and lock the mutex.
|
||||
EventHelpers::NotifyOnBackgroundError(immutable_db_options_.listeners,
|
||||
BackgroundErrorReason::kMemTable,
|
||||
&new_bg_error, &mutex_);
|
||||
if (!new_bg_error.ok()) {
|
||||
bg_error_ = new_bg_error; // stop compaction & fail any further writes
|
||||
}
|
||||
assert(!error_handler_.IsBGWorkStopped());
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
}
|
||||
@@ -736,8 +721,8 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
status = HandleWriteBufferFull(write_context);
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !bg_error_.ok())) {
|
||||
status = bg_error_;
|
||||
if (UNLIKELY(status.ok())) {
|
||||
status = error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !flush_scheduler_.Empty())) {
|
||||
@@ -1176,7 +1161,7 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
mutex_.Lock();
|
||||
}
|
||||
|
||||
while (bg_error_.ok() && write_controller_.IsStopped()) {
|
||||
while (!error_handler_.IsDBStopped() && write_controller_.IsStopped()) {
|
||||
if (write_options.no_slowdown) {
|
||||
return Status::Incomplete();
|
||||
}
|
||||
@@ -1192,7 +1177,7 @@ Status DBImpl::DelayWrite(uint64_t num_bytes,
|
||||
RecordTick(stats_, STALL_MICROS, time_delayed);
|
||||
}
|
||||
|
||||
return bg_error_;
|
||||
return error_handler_.GetBGError();
|
||||
}
|
||||
|
||||
Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
|
||||
+45
-24
@@ -260,6 +260,18 @@ class DBIter final: public Iterator {
|
||||
bool TooManyInternalKeysSkipped(bool increment = true);
|
||||
bool IsVisible(SequenceNumber sequence);
|
||||
|
||||
// CanReseekToSkip() returns whether the iterator can use the optimization
|
||||
// where it reseek by sequence number to get the next key when there are too
|
||||
// many versions. This is disabled for write unprepared because seeking to
|
||||
// sequence number does not guarantee that it is visible.
|
||||
inline bool CanReseekToSkip();
|
||||
|
||||
// MaxVisibleSequenceNumber() returns the maximum visible sequence number
|
||||
// for this snapshot. This sequence number may be greater than snapshot
|
||||
// seqno because uncommitted data written to DB for write unprepared will
|
||||
// have a higher sequence number.
|
||||
inline SequenceNumber MaxVisibleSequenceNumber();
|
||||
|
||||
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
||||
// is called
|
||||
void TempPinData() {
|
||||
@@ -506,8 +518,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey_, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
ikey_, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
@@ -537,8 +548,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey_, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
ikey_, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
@@ -578,7 +588,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
|
||||
// If we have sequentially iterated via numerous equal keys, then it's
|
||||
// better to seek so that we can avoid too many key comparisons.
|
||||
if (num_skipped > max_skip_) {
|
||||
if (num_skipped > max_skip_ && CanReseekToSkip()) {
|
||||
num_skipped = 0;
|
||||
std::string last_key;
|
||||
if (skipping) {
|
||||
@@ -645,8 +655,7 @@ bool DBIter::MergeValuesNewToOld() {
|
||||
break;
|
||||
} else if (kTypeDeletion == ikey.type || kTypeSingleDeletion == ikey.type ||
|
||||
range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
ikey, RangeDelPositioningMode::kForwardTraversal)) {
|
||||
// hit a delete with the same user key, stop right here
|
||||
// iter_ is positioned after delete
|
||||
iter_->Next();
|
||||
@@ -895,7 +904,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
// This user key has lots of entries.
|
||||
// We're going from old to new, and it's taking too long. Let's do a Seek()
|
||||
// and go from new to old. This helps when a key was overwritten many times.
|
||||
if (num_skipped >= max_skip_) {
|
||||
if (num_skipped >= max_skip_ && CanReseekToSkip()) {
|
||||
return FindValueForCurrentKeyUsingSeek();
|
||||
}
|
||||
|
||||
@@ -904,8 +913,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
case kTypeValue:
|
||||
case kTypeBlobIndex:
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey,
|
||||
RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
ikey, RangeDelPositioningMode::kBackwardTraversal)) {
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else {
|
||||
@@ -923,8 +931,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
break;
|
||||
case kTypeMerge:
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey,
|
||||
RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
ikey, RangeDelPositioningMode::kBackwardTraversal)) {
|
||||
merge_context_.Clear();
|
||||
last_key_entry_type = kTypeRangeDeletion;
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
@@ -1057,7 +1064,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
ikey, RangeDelPositioningMode::kBackwardTraversal)) {
|
||||
valid_ = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1102,8 +1109,7 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(
|
||||
ikey,
|
||||
RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
ikey, RangeDelPositioningMode::kBackwardTraversal)) {
|
||||
break;
|
||||
} else if (ikey.type == kTypeValue) {
|
||||
const Slice val = iter_->value();
|
||||
@@ -1194,7 +1200,7 @@ bool DBIter::FindUserKeyBeforeSavedKey() {
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
}
|
||||
|
||||
if (num_skipped >= max_skip_) {
|
||||
if (num_skipped >= max_skip_ && CanReseekToSkip()) {
|
||||
num_skipped = 0;
|
||||
IterKey last_key;
|
||||
last_key.SetInternalKey(ParsedInternalKey(
|
||||
@@ -1234,8 +1240,21 @@ bool DBIter::TooManyInternalKeysSkipped(bool increment) {
|
||||
}
|
||||
|
||||
bool DBIter::IsVisible(SequenceNumber sequence) {
|
||||
return sequence <= sequence_ &&
|
||||
(read_callback_ == nullptr || read_callback_->IsCommitted(sequence));
|
||||
return sequence <= MaxVisibleSequenceNumber() &&
|
||||
(read_callback_ == nullptr || read_callback_->IsVisible(sequence));
|
||||
}
|
||||
|
||||
bool DBIter::CanReseekToSkip() {
|
||||
return read_callback_ == nullptr ||
|
||||
read_callback_->MaxUnpreparedSequenceNumber() == 0;
|
||||
}
|
||||
|
||||
SequenceNumber DBIter::MaxVisibleSequenceNumber() {
|
||||
if (read_callback_ == nullptr) {
|
||||
return sequence_;
|
||||
}
|
||||
|
||||
return std::max(sequence_, read_callback_->MaxUnpreparedSequenceNumber());
|
||||
}
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
@@ -1243,20 +1262,22 @@ void DBIter::Seek(const Slice& target) {
|
||||
status_ = Status::OK();
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
|
||||
SequenceNumber seq = MaxVisibleSequenceNumber();
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(target, sequence_);
|
||||
saved_key_.SetInternalKey(target, seq);
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_lower_bound_, sequence_);
|
||||
saved_key_.SetInternalKey(*iterate_lower_bound_, seq);
|
||||
}
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->Seek(saved_key_.GetInternalKey());
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
@@ -1307,7 +1328,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekForPrev(saved_key_.GetInternalKey());
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
@@ -1356,7 +1377,7 @@ void DBIter::SeekToFirst() {
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToFirst();
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
@@ -1407,7 +1428,7 @@ void DBIter::SeekToLast() {
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekToLast();
|
||||
range_del_agg_.InvalidateTombstoneMapPositions();
|
||||
range_del_agg_.InvalidateRangeDelMapPositions();
|
||||
}
|
||||
PrevInternal();
|
||||
if (statistics_ != nullptr) {
|
||||
|
||||
+6
-4
@@ -36,7 +36,9 @@ class TestIterator : public InternalIterator {
|
||||
valid_(false),
|
||||
sequence_number_(0),
|
||||
iter_(0),
|
||||
cmp(comparator) {}
|
||||
cmp(comparator) {
|
||||
data_.reserve(16);
|
||||
}
|
||||
|
||||
void AddPut(std::string argkey, std::string argvalue) {
|
||||
Add(argkey, kTypeValue, argvalue);
|
||||
@@ -2660,7 +2662,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace1) {
|
||||
// MergeIterator::Prev() realized the mem table iterator is at its end
|
||||
// and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast",
|
||||
"MergeIterator::Prev:BeforePrev",
|
||||
[&](void* /*arg*/) { internal_iter2_->Add("z", kTypeValue, "7", 12u); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -2696,7 +2698,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace2) {
|
||||
// mem table after MergeIterator::Prev() realized the mem tableiterator is at
|
||||
// its end and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast", [&](void* /*arg*/) {
|
||||
"MergeIterator::Prev:BeforePrev", [&](void* /*arg*/) {
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 12u);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 11u);
|
||||
});
|
||||
@@ -2734,7 +2736,7 @@ TEST_F(DBIterWithMergeIterTest, InnerMergeIteratorDataRace3) {
|
||||
// mem table after MergeIterator::Prev() realized the mem table iterator is at
|
||||
// its end and before an SeekToLast() is called.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"MergeIterator::Prev:BeforeSeekToLast", [&](void* /*arg*/) {
|
||||
"MergeIterator::Prev:BeforePrev", [&](void* /*arg*/) {
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 16u, true);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 15u, true);
|
||||
internal_iter2_->Add("z", kTypeValue, "7", 14u, true);
|
||||
|
||||
+209
-2
@@ -20,7 +20,7 @@ namespace rocksdb {
|
||||
|
||||
// A dumb ReadCallback which saying every key is committed.
|
||||
class DummyReadCallback : public ReadCallback {
|
||||
bool IsCommitted(SequenceNumber /*seq*/) { return true; }
|
||||
bool IsVisible(SequenceNumber /*seq*/) override { return true; }
|
||||
};
|
||||
|
||||
// Test param:
|
||||
@@ -1035,6 +1035,60 @@ TEST_P(DBIteratorTest, DBIteratorBoundTest) {
|
||||
ASSERT_EQ(static_cast<int>(get_perf_context()->internal_delete_skipped_count), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, DBIteratorBoundMultiSeek) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.prefix_extractor = nullptr;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "0"));
|
||||
ASSERT_OK(Put("z", "0"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
|
||||
{
|
||||
std::string up_str = "foo5";
|
||||
Slice up(up_str);
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &up;
|
||||
std::unique_ptr<Iterator> iter(NewIterator(ro));
|
||||
|
||||
iter->Seek("foo1");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo1")), 0);
|
||||
|
||||
uint64_t prev_block_cache_hit =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_HIT);
|
||||
uint64_t prev_block_cache_miss =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_MISS);
|
||||
|
||||
ASSERT_GT(prev_block_cache_hit + prev_block_cache_miss, 0);
|
||||
|
||||
iter->Seek("foo4");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo4")), 0);
|
||||
ASSERT_EQ(prev_block_cache_hit,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_HIT));
|
||||
ASSERT_EQ(prev_block_cache_miss,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_MISS));
|
||||
|
||||
iter->Seek("foo2");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo2")), 0);
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().compare(Slice("foo3")), 0);
|
||||
ASSERT_EQ(prev_block_cache_hit,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_HIT));
|
||||
ASSERT_EQ(prev_block_cache_miss,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_MISS));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_P(DBIteratorTest, DBIteratorBoundOptimizationTest) {
|
||||
@@ -1483,6 +1537,26 @@ TEST_P(DBIteratorTest, PinnedDataIteratorReadAfterUpdate) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
class SliceTransformLimitedDomainGeneric : public SliceTransform {
|
||||
const char* Name() const override {
|
||||
return "SliceTransformLimitedDomainGeneric";
|
||||
}
|
||||
|
||||
Slice Transform(const Slice& src) const override {
|
||||
return Slice(src.data(), 1);
|
||||
}
|
||||
|
||||
bool InDomain(const Slice& src) const override {
|
||||
// prefix will be x????
|
||||
return src.size() >= 1;
|
||||
}
|
||||
|
||||
bool InRange(const Slice& dst) const override {
|
||||
// prefix will be x????
|
||||
return dst.size() == 1;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(DBIteratorTest, IterSeekForPrevCrossingFiles) {
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
@@ -1537,6 +1611,61 @@ TEST_P(DBIteratorTest, IterSeekForPrevCrossingFiles) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IterSeekForPrevCrossingFilesCustomPrefixExtractor) {
|
||||
Options options = CurrentOptions();
|
||||
options.prefix_extractor =
|
||||
std::make_shared<SliceTransformLimitedDomainGeneric>();
|
||||
options.disable_auto_compactions = true;
|
||||
// Enable prefix bloom for SST files
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("a1", "va1"));
|
||||
ASSERT_OK(Put("a2", "va2"));
|
||||
ASSERT_OK(Put("a3", "va3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("b1", "vb1"));
|
||||
ASSERT_OK(Put("b2", "vb2"));
|
||||
ASSERT_OK(Put("b3", "vb3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("b4", "vb4"));
|
||||
ASSERT_OK(Put("d1", "vd1"));
|
||||
ASSERT_OK(Put("d2", "vd2"));
|
||||
ASSERT_OK(Put("d4", "vd4"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
MoveFilesToLevel(1);
|
||||
{
|
||||
ReadOptions ro;
|
||||
Iterator* iter = NewIterator(ro);
|
||||
|
||||
iter->SeekForPrev("a4");
|
||||
ASSERT_EQ(iter->key().ToString(), "a3");
|
||||
ASSERT_EQ(iter->value().ToString(), "va3");
|
||||
|
||||
iter->SeekForPrev("c2");
|
||||
ASSERT_EQ(iter->key().ToString(), "b3");
|
||||
iter->SeekForPrev("d3");
|
||||
ASSERT_EQ(iter->key().ToString(), "d2");
|
||||
iter->SeekForPrev("b5");
|
||||
ASSERT_EQ(iter->key().ToString(), "b4");
|
||||
delete iter;
|
||||
}
|
||||
|
||||
{
|
||||
ReadOptions ro;
|
||||
ro.prefix_same_as_start = true;
|
||||
Iterator* iter = NewIterator(ro);
|
||||
iter->SeekForPrev("c2");
|
||||
ASSERT_TRUE(!iter->Valid());
|
||||
delete iter;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, IterPrevKeyCrossingBlocks) {
|
||||
Options options = CurrentOptions();
|
||||
BlockBasedTableOptions table_options;
|
||||
@@ -2044,6 +2173,43 @@ TEST_P(DBIteratorTest, CreationFailure) {
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, UpperBoundWithChangeDirection) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_sequential_skip_in_iterations = 3;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// write a bunch of kvs to the database.
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("y", "1"));
|
||||
ASSERT_OK(Put("y1", "1"));
|
||||
ASSERT_OK(Put("y2", "1"));
|
||||
ASSERT_OK(Put("y3", "1"));
|
||||
ASSERT_OK(Put("z", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("z", "1"));
|
||||
ASSERT_OK(Put("bar", "1"));
|
||||
ASSERT_OK(Put("foo", "1"));
|
||||
|
||||
std::string upper_bound = "x";
|
||||
Slice ub_slice(upper_bound);
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &ub_slice;
|
||||
ro.max_skippable_internal_keys = 1000;
|
||||
|
||||
Iterator* iter = NewIterator(ro);
|
||||
iter->Seek("foo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, TableFilter) {
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
@@ -2110,6 +2276,47 @@ TEST_P(DBIteratorTest, TableFilter) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, UpperBoundWithPrevReseek) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_sequential_skip_in_iterations = 3;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// write a bunch of kvs to the database.
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("y", "1"));
|
||||
ASSERT_OK(Put("z", "1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("a", "1"));
|
||||
ASSERT_OK(Put("z", "1"));
|
||||
ASSERT_OK(Put("bar", "1"));
|
||||
ASSERT_OK(Put("foo", "1"));
|
||||
ASSERT_OK(Put("foo", "2"));
|
||||
|
||||
ASSERT_OK(Put("foo", "3"));
|
||||
ASSERT_OK(Put("foo", "4"));
|
||||
ASSERT_OK(Put("foo", "5"));
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(Put("foo", "6"));
|
||||
|
||||
std::string upper_bound = "x";
|
||||
Slice ub_slice(upper_bound);
|
||||
ReadOptions ro;
|
||||
ro.snapshot = snapshot;
|
||||
ro.iterate_upper_bound = &ub_slice;
|
||||
|
||||
Iterator* iter = NewIterator(ro);
|
||||
iter->SeekForPrev("goo");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("foo", iter->key().ToString());
|
||||
iter->Prev();
|
||||
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ("bar", iter->key().ToString());
|
||||
|
||||
delete iter;
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, SkipStatistics) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
@@ -2285,7 +2492,7 @@ TEST_F(DBIteratorWithReadCallbackTest, ReadCallback) {
|
||||
explicit TestReadCallback(SequenceNumber last_visible_seq)
|
||||
: last_visible_seq_(last_visible_seq) {}
|
||||
|
||||
bool IsCommitted(SequenceNumber seq) override {
|
||||
bool IsVisible(SequenceNumber seq) override {
|
||||
return seq <= last_visible_seq_;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class TestReadCallback : public ReadCallback {
|
||||
SequenceNumber snapshot_seq)
|
||||
: snapshot_checker_(snapshot_checker), snapshot_seq_(snapshot_seq) {}
|
||||
|
||||
bool IsCommitted(SequenceNumber seq) override {
|
||||
bool IsVisible(SequenceNumber seq) override {
|
||||
return snapshot_checker_->IsInSnapshot(seq, snapshot_seq_);
|
||||
}
|
||||
|
||||
|
||||
+57
-13
@@ -170,6 +170,7 @@ void ResetTableProperties(TableProperties* tp) {
|
||||
tp->raw_value_size = 0;
|
||||
tp->num_data_blocks = 0;
|
||||
tp->num_entries = 0;
|
||||
tp->num_range_deletions = 0;
|
||||
}
|
||||
|
||||
void ParseTablePropertiesString(std::string tp_string, TableProperties* tp) {
|
||||
@@ -178,15 +179,18 @@ void ParseTablePropertiesString(std::string tp_string, TableProperties* tp) {
|
||||
std::replace(tp_string.begin(), tp_string.end(), '=', ' ');
|
||||
ResetTableProperties(tp);
|
||||
sscanf(tp_string.c_str(),
|
||||
"# data blocks %" SCNu64 " # entries %" SCNu64 " raw key size %" SCNu64
|
||||
"# data blocks %" SCNu64 " # entries %" SCNu64
|
||||
" # range deletions %" SCNu64
|
||||
" raw key size %" SCNu64
|
||||
" raw average key size %lf "
|
||||
" raw value size %" SCNu64
|
||||
" raw average value size %lf "
|
||||
" data block size %" SCNu64 " index block size (user-key? %" SCNu64
|
||||
") %" SCNu64 " filter block size %" SCNu64,
|
||||
&tp->num_data_blocks, &tp->num_entries, &tp->raw_key_size,
|
||||
&dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size,
|
||||
&tp->index_key_is_user_key, &tp->index_size, &tp->filter_size);
|
||||
&tp->num_data_blocks, &tp->num_entries, &tp->num_range_deletions,
|
||||
&tp->raw_key_size, &dummy_double, &tp->raw_value_size, &dummy_double,
|
||||
&tp->data_size, &tp->index_key_is_user_key, &tp->index_size,
|
||||
&tp->filter_size);
|
||||
}
|
||||
|
||||
void VerifySimilar(uint64_t a, uint64_t b, double bias) {
|
||||
@@ -217,20 +221,25 @@ void VerifyTableProperties(const TableProperties& base_tp,
|
||||
ASSERT_EQ(base_tp.raw_key_size, new_tp.raw_key_size);
|
||||
ASSERT_EQ(base_tp.raw_value_size, new_tp.raw_value_size);
|
||||
ASSERT_EQ(base_tp.num_entries, new_tp.num_entries);
|
||||
ASSERT_EQ(base_tp.num_range_deletions, new_tp.num_range_deletions);
|
||||
}
|
||||
|
||||
void GetExpectedTableProperties(TableProperties* expected_tp,
|
||||
const int kKeySize, const int kValueSize,
|
||||
const int kKeysPerTable, const int kTableCount,
|
||||
const int kKeysPerTable,
|
||||
const int kRangeDeletionsPerTable,
|
||||
const int kTableCount,
|
||||
const int kBloomBitsPerKey,
|
||||
const size_t kBlockSize,
|
||||
const bool index_key_is_user_key) {
|
||||
const int kKeyCount = kTableCount * kKeysPerTable;
|
||||
const int kRangeDeletionCount = kTableCount * kRangeDeletionsPerTable;
|
||||
const int kAvgSuccessorSize = kKeySize / 5;
|
||||
const int kEncodingSavePerKey = kKeySize / 4;
|
||||
expected_tp->raw_key_size = kKeyCount * (kKeySize + 8);
|
||||
expected_tp->raw_value_size = kKeyCount * kValueSize;
|
||||
expected_tp->raw_key_size = (kKeyCount + kRangeDeletionCount) * (kKeySize + 8);
|
||||
expected_tp->raw_value_size = (kKeyCount + kRangeDeletionCount) * kValueSize;
|
||||
expected_tp->num_entries = kKeyCount;
|
||||
expected_tp->num_range_deletions = kRangeDeletionCount;
|
||||
expected_tp->num_data_blocks =
|
||||
kTableCount *
|
||||
(kKeysPerTable * (kKeySize - kEncodingSavePerKey + kValueSize)) /
|
||||
@@ -252,8 +261,11 @@ TEST_F(DBPropertiesTest, ValidatePropertyInfo) {
|
||||
ASSERT_TRUE(ppt_name_and_info.first.empty() ||
|
||||
!isdigit(ppt_name_and_info.first.back()));
|
||||
|
||||
ASSERT_TRUE((ppt_name_and_info.second.handle_string == nullptr) !=
|
||||
(ppt_name_and_info.second.handle_int == nullptr));
|
||||
int count = 0;
|
||||
count += (ppt_name_and_info.second.handle_string == nullptr) ? 0 : 1;
|
||||
count += (ppt_name_and_info.second.handle_int == nullptr) ? 0 : 1;
|
||||
count += (ppt_name_and_info.second.handle_string_dbimpl == nullptr) ? 0 : 1;
|
||||
ASSERT_TRUE(count == 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +300,7 @@ TEST_F(DBPropertiesTest, ValidateSampleNumber) {
|
||||
|
||||
TEST_F(DBPropertiesTest, AggregatedTableProperties) {
|
||||
for (int kTableCount = 40; kTableCount <= 100; kTableCount += 30) {
|
||||
const int kRangeDeletionsPerTable = 5;
|
||||
const int kKeysPerTable = 100;
|
||||
const int kKeySize = 80;
|
||||
const int kValueSize = 200;
|
||||
@@ -306,12 +319,22 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) {
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Hold open a snapshot to prevent range tombstones from being compacted
|
||||
// away.
|
||||
ManagedSnapshot snapshot(db_);
|
||||
|
||||
Random rnd(5632);
|
||||
for (int table = 1; table <= kTableCount; ++table) {
|
||||
for (int i = 0; i < kKeysPerTable; ++i) {
|
||||
db_->Put(WriteOptions(), RandomString(&rnd, kKeySize),
|
||||
RandomString(&rnd, kValueSize));
|
||||
}
|
||||
for (int i = 0; i < kRangeDeletionsPerTable; i++) {
|
||||
std::string start = RandomString(&rnd, kKeySize);
|
||||
std::string end = start;
|
||||
end.resize(kValueSize);
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end);
|
||||
}
|
||||
db_->Flush(FlushOptions());
|
||||
}
|
||||
std::string property;
|
||||
@@ -322,7 +345,8 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) {
|
||||
|
||||
TableProperties expected_tp;
|
||||
GetExpectedTableProperties(&expected_tp, kKeySize, kValueSize,
|
||||
kKeysPerTable, kTableCount, kBloomBitsPerKey,
|
||||
kKeysPerTable, kRangeDeletionsPerTable,
|
||||
kTableCount, kBloomBitsPerKey,
|
||||
table_options.block_size, index_key_is_user_key);
|
||||
|
||||
VerifyTableProperties(expected_tp, output_tp);
|
||||
@@ -372,6 +396,13 @@ TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) {
|
||||
for (int key = 0; key < key_index; key++) {
|
||||
Get(Key(key));
|
||||
}
|
||||
|
||||
// Test for getting immutable_db_options_.statistics
|
||||
ASSERT_TRUE(dbfull()->GetProperty(dbfull()->DefaultColumnFamily(),
|
||||
"rocksdb.options-statistics", &prop));
|
||||
ASSERT_NE(std::string::npos, prop.find("rocksdb.block.cache.miss"));
|
||||
ASSERT_EQ(std::string::npos, prop.find("rocksdb.db.f.micros"));
|
||||
|
||||
ASSERT_TRUE(dbfull()->GetProperty(dbfull()->DefaultColumnFamily(),
|
||||
"rocksdb.cf-file-histogram", &prop));
|
||||
ASSERT_NE(std::string::npos, prop.find("** Level 0 read latency histogram"));
|
||||
@@ -438,6 +469,7 @@ TEST_F(DBPropertiesTest, ReadLatencyHistogramByLevel) {
|
||||
|
||||
TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
const int kTableCount = 100;
|
||||
const int kRangeDeletionsPerTable = 2;
|
||||
const int kKeysPerTable = 10;
|
||||
const int kKeySize = 50;
|
||||
const int kValueSize = 400;
|
||||
@@ -463,6 +495,9 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Hold open a snapshot to prevent range tombstones from being compacted away.
|
||||
ManagedSnapshot snapshot(db_);
|
||||
|
||||
std::string level_tp_strings[kMaxLevel];
|
||||
std::string tp_string;
|
||||
TableProperties level_tps[kMaxLevel];
|
||||
@@ -472,6 +507,12 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
db_->Put(WriteOptions(), RandomString(&rnd, kKeySize),
|
||||
RandomString(&rnd, kValueSize));
|
||||
}
|
||||
for (int i = 0; i < kRangeDeletionsPerTable; i++) {
|
||||
std::string start = RandomString(&rnd, kKeySize);
|
||||
std::string end = start;
|
||||
end.resize(kValueSize);
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), start, end);
|
||||
}
|
||||
db_->Flush(FlushOptions());
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ResetTableProperties(&sum_tp);
|
||||
@@ -487,6 +528,7 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
sum_tp.raw_value_size += level_tps[level].raw_value_size;
|
||||
sum_tp.num_data_blocks += level_tps[level].num_data_blocks;
|
||||
sum_tp.num_entries += level_tps[level].num_entries;
|
||||
sum_tp.num_range_deletions += level_tps[level].num_range_deletions;
|
||||
}
|
||||
db_->GetProperty(DB::Properties::kAggregatedTableProperties, &tp_string);
|
||||
ParseTablePropertiesString(tp_string, &tp);
|
||||
@@ -498,13 +540,15 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
ASSERT_EQ(sum_tp.raw_value_size, tp.raw_value_size);
|
||||
ASSERT_EQ(sum_tp.num_data_blocks, tp.num_data_blocks);
|
||||
ASSERT_EQ(sum_tp.num_entries, tp.num_entries);
|
||||
ASSERT_EQ(sum_tp.num_range_deletions, tp.num_range_deletions);
|
||||
if (table > 3) {
|
||||
GetExpectedTableProperties(
|
||||
&expected_tp, kKeySize, kValueSize, kKeysPerTable, table,
|
||||
kBloomBitsPerKey, table_options.block_size, index_key_is_user_key);
|
||||
&expected_tp, kKeySize, kValueSize, kKeysPerTable,
|
||||
kRangeDeletionsPerTable, table, kBloomBitsPerKey,
|
||||
table_options.block_size, index_key_is_user_key);
|
||||
// Gives larger bias here as index block size, filter block size,
|
||||
// and data block size become much harder to estimate in this test.
|
||||
VerifyTableProperties(tp, expected_tp, 0.5, 0.4, 0.4, 0.25);
|
||||
VerifyTableProperties(expected_tp, tp, 0.5, 0.4, 0.4, 0.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+179
-72
@@ -27,47 +27,53 @@ class DBRangeDelTest : public DBTestBase {
|
||||
// ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBRangeDelTest, NonBlockBasedTableNotSupported) {
|
||||
if (!IsMemoryMappedAccessSupported()) {
|
||||
return;
|
||||
// TODO: figure out why MmapReads trips the iterator pinning assertion in
|
||||
// RangeDelAggregator. Ideally it would be supported; otherwise it should at
|
||||
// least be explicitly unsupported.
|
||||
for (auto config : {kPlainTableAllBytesPrefix, /* kWalDirAndMmapReads */}) {
|
||||
option_config_ = config;
|
||||
DestroyAndReopen(CurrentOptions());
|
||||
ASSERT_TRUE(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
"dr1", "dr1")
|
||||
.IsNotSupported());
|
||||
}
|
||||
Options opts = CurrentOptions();
|
||||
opts.table_factory.reset(new PlainTableFactory());
|
||||
opts.prefix_extractor.reset(NewNoopTransform());
|
||||
opts.allow_mmap_reads = true;
|
||||
opts.max_sequential_skip_in_iterations = 999999;
|
||||
Reopen(opts);
|
||||
|
||||
ASSERT_TRUE(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1", "dr1")
|
||||
.IsNotSupported());
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, FlushOutputHasOnlyRangeTombstones) {
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1",
|
||||
"dr2"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
do {
|
||||
DestroyAndReopen(CurrentOptions());
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
"dr1", "dr2"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionOutputHasOnlyRangeTombstone) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.statistics = CreateDBStatistics();
|
||||
Reopen(opts);
|
||||
do {
|
||||
Options opts = CurrentOptions();
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z");
|
||||
db_->Flush(FlushOptions());
|
||||
// snapshot protects range tombstone from dropping due to becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z");
|
||||
db_->Flush(FlushOptions());
|
||||
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow_trivial_move */);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(0, TestGetTickerCount(opts, COMPACTION_RANGE_DEL_DROP_OBSOLETE));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow_trivial_move */);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(0, TestGetTickerCount(opts, COMPACTION_RANGE_DEL_DROP_OBSOLETE));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
// Skip cuckoo memtables, which do not support snapshots. Skip non-leveled
|
||||
// compactions as the above assertions about the number of files in a level
|
||||
// do not hold true.
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs | kSkipHashCuckoo |
|
||||
kSkipUniversalCompaction | kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionOutputFilesExactlyFilled) {
|
||||
@@ -496,12 +502,12 @@ TEST_F(DBRangeDelTest, ObsoleteTombstoneCleanup) {
|
||||
Reopen(opts);
|
||||
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr1",
|
||||
"dr1"); // obsolete after compaction
|
||||
"dr10"); // obsolete after compaction
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
db_->Flush(FlushOptions());
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "dr2",
|
||||
"dr2"); // protected by snapshot
|
||||
"dr20"); // protected by snapshot
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
db_->Flush(FlushOptions());
|
||||
|
||||
@@ -590,48 +596,57 @@ TEST_F(DBRangeDelTest, TableEvictedDuringScan) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredKeyFromMutableMemtable) {
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
do {
|
||||
DestroyAndReopen(CurrentOptions());
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredKeyFromImmutableMemtable) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.max_write_buffer_number = 3;
|
||||
opts.min_write_buffer_number_to_merge = 2;
|
||||
// SpecialSkipListFactory lets us specify maximum number of elements the
|
||||
// memtable can hold. It switches the active memtable to immutable (flush is
|
||||
// prevented by the above options) upon inserting an element that would
|
||||
// overflow the memtable.
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
Reopen(opts);
|
||||
do {
|
||||
Options opts = CurrentOptions();
|
||||
opts.max_write_buffer_number = 3;
|
||||
opts.min_write_buffer_number_to_merge = 2;
|
||||
// SpecialSkipListFactory lets us specify maximum number of elements the
|
||||
// memtable can hold. It switches the active memtable to immutable (flush is
|
||||
// prevented by the above options) upon inserting an element that would
|
||||
// overflow the memtable.
|
||||
opts.memtable_factory.reset(new SpecialSkipListFactory(1));
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
db_->Put(WriteOptions(), "blah", "val");
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
db_->Put(WriteOptions(), "blah", "val");
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredKeyFromSst) {
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
// snapshot prevents key from being deleted during flush
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
do {
|
||||
DestroyAndReopen(CurrentOptions());
|
||||
db_->Put(WriteOptions(), "key", "val");
|
||||
// snapshot prevents key from being deleted during flush
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "z"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
ReadOptions read_opts;
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(read_opts, "key", &value).IsNotFound());
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
// Cuckoo memtables do not support snapshots.
|
||||
} while (ChangeOptions(kRangeDelSkipConfigs | kSkipHashCuckoo));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, GetCoveredMergeOperandFromMemtable) {
|
||||
@@ -895,11 +910,14 @@ TEST_F(DBRangeDelTest, MemtableBloomFilter) {
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) {
|
||||
// make sure compaction treats files containing a split range deletion in the
|
||||
// input level as an atomic unit. I.e., compacting any input-level file(s)
|
||||
// containing a portion of the range deletion causes all other input-level
|
||||
// files containing portions of that same range deletion to be included in the
|
||||
// compaction.
|
||||
// This test originally verified that compaction treated files containing a
|
||||
// split range deletion in the input level as an atomic unit. I.e.,
|
||||
// compacting any input-level file(s) containing a portion of the range
|
||||
// deletion causes all other input-level files containing portions of that
|
||||
// same range deletion to be included in the compaction. Range deletion
|
||||
// tombstones are now truncated to sstable boundaries which removed the need
|
||||
// for that behavior (which could lead to excessively large
|
||||
// compactions).
|
||||
const int kNumFilesPerLevel = 4, kValueBytes = 4 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
@@ -946,22 +964,111 @@ TEST_F(DBRangeDelTest, CompactionTreatsSplitInputLevelDeletionAtomically) {
|
||||
if (i == 0) {
|
||||
ASSERT_OK(db_->CompactFiles(
|
||||
CompactionOptions(), {meta.levels[1].files[0].name}, 2 /* level */));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
} else if (i == 1) {
|
||||
auto begin_str = Key(0), end_str = Key(1);
|
||||
Slice begin = begin_str, end = end_str;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &begin, &end));
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(1));
|
||||
} else if (i == 2) {
|
||||
ASSERT_OK(db_->SetOptions(db_->DefaultColumnFamily(),
|
||||
{{"max_bytes_for_level_base", "10000"}}));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
}
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
ASSERT_GT(NumTableFilesAtLevel(2), 0);
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, RangeTombstoneEndKeyAsSstableUpperBound) {
|
||||
// Test the handling of the range-tombstone end-key as the
|
||||
// upper-bound for an sstable.
|
||||
|
||||
const int kNumFilesPerLevel = 2, kValueBytes = 4 << 10;
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = kNumFilesPerLevel;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(2 /* num_entries_flush */));
|
||||
options.target_file_size_base = kValueBytes;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create an initial sstable at L2:
|
||||
// [key000000#1,1, key000000#1,1]
|
||||
ASSERT_OK(Put(Key(0), ""));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(2));
|
||||
|
||||
// A snapshot protects the range tombstone from dropping due to
|
||||
// becoming obsolete.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(0), Key(2 * kNumFilesPerLevel));
|
||||
|
||||
// Create 2 additional sstables in L0. Note that the first sstable
|
||||
// contains the range tombstone.
|
||||
// [key000000#3,1, key000004#72057594037927935,15]
|
||||
// [key000001#5,1, key000002#6,1]
|
||||
Random rnd(301);
|
||||
std::string value = RandomString(&rnd, kValueBytes);
|
||||
for (int j = 0; j < kNumFilesPerLevel; ++j) {
|
||||
// Give files overlapping key-ranges to prevent a trivial move when we
|
||||
// compact from L0 to L1.
|
||||
ASSERT_OK(Put(Key(j), value));
|
||||
ASSERT_OK(Put(Key(2 * kNumFilesPerLevel - 1 - j), value));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
ASSERT_EQ(j + 1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
// Compact the 2 L0 sstables to L1, resulting in the following LSM. There
|
||||
// are 2 sstables generated in L1 due to the target_file_size_base setting.
|
||||
// L1:
|
||||
// [key000000#3,1, key000002#72057594037927935,15]
|
||||
// [key000002#6,1, key000004#72057594037927935,15]
|
||||
// L2:
|
||||
// [key000000#1,1, key000000#1,1]
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(1));
|
||||
|
||||
{
|
||||
// Compact the second sstable in L1:
|
||||
// L1:
|
||||
// [key000000#3,1, key000002#72057594037927935,15]
|
||||
// L2:
|
||||
// [key000000#1,1, key000000#1,1]
|
||||
// [key000002#6,1, key000004#72057594037927935,15]
|
||||
auto begin_str = Key(3);
|
||||
const rocksdb::Slice begin = begin_str;
|
||||
dbfull()->TEST_CompactRange(1, &begin, nullptr);
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(2));
|
||||
}
|
||||
|
||||
{
|
||||
// Compact the first sstable in L1. This should be copacetic, but
|
||||
// was previously resulting in overlapping sstables in L2 due to
|
||||
// mishandling of the range tombstone end-key when used as the
|
||||
// largest key for an sstable. The resulting LSM structure should
|
||||
// be:
|
||||
//
|
||||
// L2:
|
||||
// [key000000#1,1, key000001#72057594037927935,15]
|
||||
// [key000001#5,1, key000002#72057594037927935,15]
|
||||
// [key000002#6,1, key000004#72057594037927935,15]
|
||||
auto begin_str = Key(0);
|
||||
const rocksdb::Slice begin = begin_str;
|
||||
dbfull()->TEST_CompactRange(1, &begin, &begin);
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(1));
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(2));
|
||||
}
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, UnorderedTombstones) {
|
||||
// Regression test for #2752. Range delete tombstones between
|
||||
// different snapshot stripes are not stored in order, so the first
|
||||
|
||||
+3
-176
@@ -1682,7 +1682,7 @@ TEST_F(DBTest, CustomComparator) {
|
||||
|
||||
TEST_F(DBTest, DBOpen_Options) {
|
||||
Options options = CurrentOptions();
|
||||
std::string dbname = test::TmpDir(env_) + "/db_options_test";
|
||||
std::string dbname = test::PerThreadDBPath("db_options_test");
|
||||
ASSERT_OK(DestroyDB(dbname, options));
|
||||
|
||||
// Does not exist, and create_if_missing == false: error
|
||||
@@ -1740,7 +1740,7 @@ TEST_F(DBTest, DBOpen_Change_NumLevels) {
|
||||
}
|
||||
|
||||
TEST_F(DBTest, DestroyDBMetaDatabase) {
|
||||
std::string dbname = test::TmpDir(env_) + "/db_meta";
|
||||
std::string dbname = test::PerThreadDBPath("db_meta");
|
||||
ASSERT_OK(env_->CreateDirIfMissing(dbname));
|
||||
std::string metadbname = MetaDatabaseName(dbname, 0);
|
||||
ASSERT_OK(env_->CreateDirIfMissing(metadbname));
|
||||
@@ -4607,180 +4607,6 @@ TEST_F(DBTest, FileCreationRandomFailure) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
int CountIter(Iterator* iter, const Slice& key) {
|
||||
int count = 0;
|
||||
for (iter->Seek(key); iter->Valid() && iter->status() == Status::OK();
|
||||
iter->Next()) {
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Create multiple SST files each with a different prefix_extractor config,
|
||||
// verify iterators can read all SST files using the latest config.
|
||||
TEST_F(DBTest, DynamicBloomFilterMultipleSST) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
// Enable prefix bloom for SST files
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
|
||||
// first SST with fixed:1 BF
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foq1", "bar1"));
|
||||
ASSERT_OK(Put("fpa", "0"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
Iterator* iter_old = db_->NewIterator(read_options);
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 4);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
Iterator* iter = db_->NewIterator(read_options);
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
|
||||
// second SST with capped:3 BF
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Put("foq5", "bar5"));
|
||||
ASSERT_OK(Put("fpb", "1"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
// BF is cappped:3 now
|
||||
Iterator* iter_tmp = db_->NewIterator(read_options);
|
||||
ASSERT_EQ(CountIter(iter_tmp, "foo"), 4);
|
||||
delete iter_tmp;
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.2"));
|
||||
// third SST with fixed:2 BF
|
||||
ASSERT_OK(Put("foo6", "bar6"));
|
||||
ASSERT_OK(Put("foo7", "bar7"));
|
||||
ASSERT_OK(Put("foq8", "bar8"));
|
||||
ASSERT_OK(Put("fpc", "2"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
// BF is fixed:2 now
|
||||
iter_tmp = db_->NewIterator(read_options);
|
||||
ASSERT_EQ(CountIter(iter_tmp, "foo"), 9);
|
||||
delete iter_tmp;
|
||||
|
||||
// TODO(Zhongyi): verify existing iterator cannot see newly inserted keys
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 4);
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
delete iter;
|
||||
delete iter_old;
|
||||
|
||||
// keys in all three SSTs are visible to iterator
|
||||
Iterator* iter_all = db_->NewIterator(read_options);
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 9);
|
||||
delete iter_all;
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
iter_all = db_->NewIterator(read_options);
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 6);
|
||||
delete iter_all;
|
||||
// TODO(Zhongyi): add test for cases where certain SST are skipped
|
||||
// Also verify BF related counters like BLOOM_FILTER_USEFUL
|
||||
}
|
||||
|
||||
// Create a new column family in a running DB, change prefix_extractor
|
||||
// dynamically, verify the iterator created on the new column family behaves
|
||||
// as expected
|
||||
TEST_F(DBTest, DynamicBloomFilterNewColumnFamily) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
// Enable prefix bloom for SST files
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
// create a new CF and set prefix_extractor dynamically
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(3));
|
||||
CreateColumnFamilies({"ramen_dojo"}, options);
|
||||
ASSERT_EQ(0,
|
||||
strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_OK(Put(2, "foo3", "bar3"));
|
||||
ASSERT_OK(Put(2, "foo4", "bar4"));
|
||||
ASSERT_OK(Put(2, "foo5", "bar5"));
|
||||
ASSERT_OK(Put(2, "foq6", "bar6"));
|
||||
ASSERT_OK(Put(2, "fpq7", "bar7"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
Iterator* iter = db_->NewIterator(read_options, handles_[2]);
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 3);
|
||||
delete iter;
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[2], {{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ(0,
|
||||
strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.2"));
|
||||
iter = db_->NewIterator(read_options, handles_[2]);
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 4);
|
||||
delete iter;
|
||||
}
|
||||
|
||||
// Verify it's possible to change prefix_extractor at runtime and iterators
|
||||
// behaves as expected
|
||||
TEST_F(DBTest, DynamicBloomFilterOptions) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
// Enable prefix bloom for SST files
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, true));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Put("foo1", "bar1"));
|
||||
ASSERT_OK(Put("fpa", "0"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(Put("foo3", "bar3"));
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Put("foo5", "bar5"));
|
||||
ASSERT_OK(Put("fpb", "1"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(Put("foo6", "bar6"));
|
||||
ASSERT_OK(Put("foo7", "bar7"));
|
||||
ASSERT_OK(Put("foo8", "bar8"));
|
||||
ASSERT_OK(Put("fpc", "2"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.prefix_same_as_start = true;
|
||||
Iterator* iter = db_->NewIterator(read_options);
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 12);
|
||||
delete iter;
|
||||
Iterator* iter_old = db_->NewIterator(read_options);
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 12);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
iter = db_->NewIterator(read_options);
|
||||
// "fp*" should be skipped
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 9);
|
||||
delete iter;
|
||||
|
||||
// iterator created before should not be affected and see all keys
|
||||
ASSERT_EQ(CountIter(iter_old, "foo"), 12);
|
||||
delete iter_old;
|
||||
}
|
||||
|
||||
TEST_F(DBTest, DynamicMiscOptions) {
|
||||
// Test max_sequential_skip_in_iterations
|
||||
@@ -5099,6 +4925,7 @@ TEST_P(DBTestWithParam, FilterCompactionTimeTest) {
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.statistics->stats_level_ = kExceptTimeForMutex;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
+47
-2
@@ -354,7 +354,7 @@ INSTANTIATE_TEST_CASE_P(DBTestSharedWriteBufferAcrossCFs,
|
||||
std::make_tuple(false, true)));
|
||||
|
||||
TEST_F(DBTest2, SharedWriteBufferLimitAcrossDB) {
|
||||
std::string dbname2 = test::TmpDir(env_) + "/db_shared_wb_db2";
|
||||
std::string dbname2 = test::PerThreadDBPath("db_shared_wb_db2");
|
||||
Options options = CurrentOptions();
|
||||
options.arena_block_size = 4096;
|
||||
// Avoid undeterministic value by malloc_usable_size();
|
||||
@@ -2422,7 +2422,7 @@ TEST_F(DBTest2, ReadCallbackTest) {
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
explicit TestReadCallback(SequenceNumber snapshot) : snapshot_(snapshot) {}
|
||||
virtual bool IsCommitted(SequenceNumber seq) override {
|
||||
virtual bool IsVisible(SequenceNumber seq) override {
|
||||
return seq <= snapshot_;
|
||||
}
|
||||
|
||||
@@ -2502,6 +2502,51 @@ TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, PinnableSliceAndMmapReads) {
|
||||
Options options = CurrentOptions();
|
||||
options.allow_mmap_reads = true;
|
||||
options.max_open_files = 100;
|
||||
options.compression = kNoCompression;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
PinnableSlice pinned_value;
|
||||
ASSERT_EQ(Get("foo", &pinned_value), Status::OK());
|
||||
// It is not safe to pin mmap files as they might disappear by compaction
|
||||
ASSERT_FALSE(pinned_value.IsPinned());
|
||||
ASSERT_EQ(pinned_value.ToString(), "bar");
|
||||
|
||||
dbfull()->TEST_CompactRange(0 /* level */, nullptr /* begin */,
|
||||
nullptr /* end */, nullptr /* column_family */,
|
||||
true /* disallow_trivial_move */);
|
||||
|
||||
// Ensure pinned_value doesn't rely on memory munmap'd by the above
|
||||
// compaction. It crashes if it does.
|
||||
ASSERT_EQ(pinned_value.ToString(), "bar");
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
pinned_value.Reset();
|
||||
// Unsafe to pin mmap files when they could be kicked out of table cache
|
||||
Close();
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_EQ(Get("foo", &pinned_value), Status::OK());
|
||||
ASSERT_FALSE(pinned_value.IsPinned());
|
||||
ASSERT_EQ(pinned_value.ToString(), "bar");
|
||||
|
||||
pinned_value.Reset();
|
||||
// In read-only mode with infinite capacity on table cache it should pin the
|
||||
// value and avoid the memcpy
|
||||
Close();
|
||||
options.max_open_files = -1;
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_EQ(Get("foo", &pinned_value), Status::OK());
|
||||
ASSERT_TRUE(pinned_value.IsPinned());
|
||||
ASSERT_EQ(pinned_value.ToString(), "bar");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+34
-22
@@ -63,7 +63,7 @@ DBTestBase::DBTestBase(const std::string path)
|
||||
option_config_(kDefault) {
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
dbname_ = test::TmpDir(env_) + path;
|
||||
dbname_ = test::PerThreadDBPath(env_, path);
|
||||
alternative_wal_dir_ = dbname_ + "/wal";
|
||||
alternative_db_log_dir_ = dbname_ + "/db_log_dir";
|
||||
auto options = CurrentOptions();
|
||||
@@ -118,7 +118,8 @@ bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) {
|
||||
|
||||
if ((skip_mask & kSkipUniversalCompaction) &&
|
||||
(option_config == kUniversalCompaction ||
|
||||
option_config == kUniversalCompactionMultiLevel)) {
|
||||
option_config == kUniversalCompactionMultiLevel ||
|
||||
option_config == kUniversalSubcompactions)) {
|
||||
return true;
|
||||
}
|
||||
if ((skip_mask & kSkipMergePut) && option_config == kMergePut) {
|
||||
@@ -346,6 +347,26 @@ Options DBTestBase::GetOptions(
|
||||
NewHashCuckooRepFactory(options.write_buffer_size));
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
break;
|
||||
case kDirectIO: {
|
||||
options.use_direct_reads = true;
|
||||
options.use_direct_io_for_flush_and_compaction = true;
|
||||
options.compaction_readahead_size = 2 * 1024 * 1024;
|
||||
#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \
|
||||
!defined(OS_AIX) && !defined(OS_OPENBSD)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile:O_DIRECT", [&](void* arg) {
|
||||
int* val = static_cast<int*>(arg);
|
||||
*val &= ~O_DIRECT;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewRandomAccessFile:O_DIRECT", [&](void* arg) {
|
||||
int* val = static_cast<int*>(arg);
|
||||
*val &= ~O_DIRECT;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
case kMergePut:
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
@@ -428,6 +449,17 @@ Options DBTestBase::GetOptions(
|
||||
options.prefix_extractor.reset(NewNoopTransform());
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithPartitionedIndexFormat3: {
|
||||
table_options.format_version = 3;
|
||||
// Format 3 changes the binary index format. Since partitioned index is a
|
||||
// super-set of simple indexes, we are also using kTwoLevelIndexSearch to
|
||||
// test this format.
|
||||
table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
|
||||
// The top-level index in partition filters are also affected by format 3.
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
table_options.partition_filters = true;
|
||||
break;
|
||||
}
|
||||
case kBlockBasedTableWithIndexRestartInterval: {
|
||||
table_options.index_block_restart_interval = 8;
|
||||
break;
|
||||
@@ -460,26 +492,6 @@ Options DBTestBase::GetOptions(
|
||||
options.enable_write_thread_adaptive_yield = true;
|
||||
break;
|
||||
}
|
||||
case kDirectIO: {
|
||||
options.use_direct_reads = true;
|
||||
options.use_direct_io_for_flush_and_compaction = true;
|
||||
options.compaction_readahead_size = 2 * 1024 * 1024;
|
||||
#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \
|
||||
!defined(OS_AIX) && !defined(OS_OPENBSD)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile:O_DIRECT", [&](void* arg) {
|
||||
int* val = static_cast<int*>(arg);
|
||||
*val &= ~O_DIRECT;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewRandomAccessFile:O_DIRECT", [&](void* arg) {
|
||||
int* val = static_cast<int*>(arg);
|
||||
*val &= ~O_DIRECT;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case kPipelinedWrite: {
|
||||
options.enable_pipelined_write = true;
|
||||
break;
|
||||
|
||||
+16
-7
@@ -694,13 +694,15 @@ class DBTestBase : public testing::Test {
|
||||
kConcurrentSkipList = 29,
|
||||
kPipelinedWrite = 30,
|
||||
kConcurrentWALWrites = 31,
|
||||
kEnd = 32,
|
||||
kDirectIO = 33,
|
||||
kLevelSubcompactions = 34,
|
||||
kUniversalSubcompactions = 35,
|
||||
kBlockBasedTableWithIndexRestartInterval = 36,
|
||||
kBlockBasedTableWithPartitionedIndex = 37,
|
||||
kPartitionedFilterWithNewTableReaderForCompactions = 38,
|
||||
kDirectIO,
|
||||
kLevelSubcompactions,
|
||||
kBlockBasedTableWithIndexRestartInterval,
|
||||
kBlockBasedTableWithPartitionedIndex,
|
||||
kBlockBasedTableWithPartitionedIndexFormat3,
|
||||
kPartitionedFilterWithNewTableReaderForCompactions,
|
||||
kUniversalSubcompactions,
|
||||
// This must be the last line
|
||||
kEnd,
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -731,6 +733,13 @@ class DBTestBase : public testing::Test {
|
||||
kSkipMmapReads = 256,
|
||||
};
|
||||
|
||||
const int kRangeDelSkipConfigs =
|
||||
// Plain tables do not support range deletions.
|
||||
kSkipPlainTable |
|
||||
// MmapReads disables the iterator pinning that RangeDelAggregator
|
||||
// requires.
|
||||
kSkipMmapReads;
|
||||
|
||||
explicit DBTestBase(const std::string path);
|
||||
|
||||
~DBTestBase();
|
||||
|
||||
@@ -1363,7 +1363,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionCFPathUse) {
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.compaction_options_universal.size_ratio = 10;
|
||||
options.write_buffer_size = 111 << 10; // 114KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
@@ -1781,6 +1781,63 @@ TEST_P(DBTestUniversalCompaction, RecalculateScoreAfterPicking) {
|
||||
ASSERT_EQ(NumSortedRuns(), 5);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, FinalSortedRunCompactFilesConflict) {
|
||||
// Regression test for conflict between:
|
||||
// (1) Running CompactFiles including file in the final sorted run; and
|
||||
// (2) Picking universal size-amp-triggered compaction, which always includes
|
||||
// the final sorted run.
|
||||
if (exclusive_manual_compaction_) {
|
||||
return;
|
||||
}
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.compaction_style = kCompactionStyleUniversal;
|
||||
opts.compaction_options_universal.max_size_amplification_percent = 50;
|
||||
opts.compaction_options_universal.min_merge_width = 2;
|
||||
opts.compression = kNoCompression;
|
||||
opts.level0_file_num_compaction_trigger = 2;
|
||||
opts.max_background_compactions = 2;
|
||||
opts.num_levels = num_levels_;
|
||||
Reopen(opts);
|
||||
|
||||
// make sure compaction jobs can be parallelized
|
||||
auto stop_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
|
||||
Put("key", "val");
|
||||
Flush();
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(num_levels_ - 1), 1);
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
ColumnFamilyHandle* default_cfh = db_->DefaultColumnFamily();
|
||||
dbfull()->GetColumnFamilyMetaData(default_cfh, &cf_meta);
|
||||
ASSERT_EQ(1, cf_meta.levels[num_levels_ - 1].files.size());
|
||||
std::string first_sst_filename =
|
||||
cf_meta.levels[num_levels_ - 1].files[0].name;
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"CompactFilesImpl:0",
|
||||
"DBTestUniversalCompaction:FinalSortedRunCompactFilesConflict:0"},
|
||||
{"DBImpl::BackgroundCompaction():AfterPickCompaction",
|
||||
"CompactFilesImpl:1"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread compact_files_thread([&]() {
|
||||
ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), default_cfh,
|
||||
{first_sst_filename}, num_levels_ - 1));
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBTestUniversalCompaction:FinalSortedRunCompactFilesConflict:0");
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Put("key", "val");
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
compact_files_thread.join();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(UniversalCompactionNumLevels, DBTestUniversalCompaction,
|
||||
::testing::Combine(::testing::Values(1, 3, 5),
|
||||
::testing::Bool()));
|
||||
|
||||
+18
-4
@@ -55,7 +55,11 @@ enum ValueType : unsigned char {
|
||||
// is not mistakenly read by another, which would result into data
|
||||
// inconsistency.
|
||||
kTypeBeginPersistedPrepareXID = 0x12, // WAL only.
|
||||
kMaxValue = 0x7F // Not used for storing records.
|
||||
// Similar to kTypeBeginPersistedPrepareXID, this is to ensure that WAL
|
||||
// generated by WriteUnprepared write policy is not mistakenly read by
|
||||
// another.
|
||||
kTypeBeginUnprepareXID = 0x13, // WAL only.
|
||||
kMaxValue = 0x7F // Not used for storing records.
|
||||
};
|
||||
|
||||
// Defined in dbformat.cc
|
||||
@@ -136,10 +140,14 @@ inline Slice ExtractUserKey(const Slice& internal_key) {
|
||||
return Slice(internal_key.data(), internal_key.size() - 8);
|
||||
}
|
||||
|
||||
inline ValueType ExtractValueType(const Slice& internal_key) {
|
||||
inline uint64_t ExtractInternalKeyFooter(const Slice& internal_key) {
|
||||
assert(internal_key.size() >= 8);
|
||||
const size_t n = internal_key.size();
|
||||
uint64_t num = DecodeFixed64(internal_key.data() + n - 8);
|
||||
return DecodeFixed64(internal_key.data() + n - 8);
|
||||
}
|
||||
|
||||
inline ValueType ExtractValueType(const Slice& internal_key) {
|
||||
uint64_t num = ExtractInternalKeyFooter(internal_key);
|
||||
unsigned char c = num & 0xff;
|
||||
return static_cast<ValueType>(c);
|
||||
}
|
||||
@@ -602,9 +610,15 @@ struct RangeTombstone {
|
||||
return InternalKey(start_key_, seq_, kTypeRangeDeletion);
|
||||
}
|
||||
|
||||
// The tombstone end-key is exclusive, so we generate an internal-key here
|
||||
// which has a similar property. Using kMaxSequenceNumber guarantees that
|
||||
// the returned internal-key will compare less than any other internal-key
|
||||
// with the same user-key. This in turn guarantees that the serialized
|
||||
// end-key for a tombstone such as [a-b] will compare less than the key "b".
|
||||
//
|
||||
// be careful to use SerializeEndKey(), allocates new memory
|
||||
InternalKey SerializeEndKey() const {
|
||||
return InternalKey(end_key_, seq_, kTypeRangeDeletion);
|
||||
return InternalKey(end_key_, kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -192,6 +192,13 @@ TEST_F(FormatTest, UpdateInternalKey) {
|
||||
ASSERT_EQ(new_val_type, decoded.type);
|
||||
}
|
||||
|
||||
TEST_F(FormatTest, RangeTombstoneSerializeEndKey) {
|
||||
RangeTombstone t("a", "b", 2);
|
||||
InternalKey k("b", 3, kTypeValue);
|
||||
const InternalKeyComparator cmp(BytewiseComparator());
|
||||
ASSERT_LT(cmp.Compare(t.SerializeEndKey(), k), 0);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -45,7 +45,7 @@ class DeleteFileTest : public testing::Test {
|
||||
options_.max_bytes_for_level_base = 1024*1024*1000;
|
||||
options_.WAL_ttl_seconds = 300; // Used to test log files
|
||||
options_.WAL_size_limit_MB = 1024; // Used to test log files
|
||||
dbname_ = test::TmpDir() + "/deletefile_test";
|
||||
dbname_ = test::PerThreadDBPath("deletefile_test");
|
||||
options_.wal_dir = dbname_ + "/wal_files";
|
||||
|
||||
// clean up all the files that might have been there before
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
// Copyright (c) 2018-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/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Maps to help decide the severity of an error based on the
|
||||
// BackgroundErrorReason, Code, SubCode and whether db_options.paranoid_checks
|
||||
// is set or not. There are 3 maps, going from most specific to least specific
|
||||
// (i.e from all 4 fields in a tuple to only the BackgroundErrorReason and
|
||||
// paranoid_checks). The less specific map serves as a catch all in case we miss
|
||||
// a specific error code or subcode.
|
||||
std::map<std::tuple<BackgroundErrorReason, Status::Code, Status::SubCode, bool>,
|
||||
Status::Severity>
|
||||
ErrorSeverityMap = {
|
||||
// Errors during BG compaction
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
true),
|
||||
Status::Severity::kSoftError},
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
false),
|
||||
Status::Severity::kNoError},
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction,
|
||||
Status::Code::kIOError, Status::SubCode::kSpaceLimit,
|
||||
true),
|
||||
Status::Severity::kHardError},
|
||||
// Errors during BG flush
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
|
||||
Status::SubCode::kNoSpace, true),
|
||||
Status::Severity::kSoftError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
|
||||
Status::SubCode::kNoSpace, false),
|
||||
Status::Severity::kNoError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, Status::Code::kIOError,
|
||||
Status::SubCode::kSpaceLimit, true),
|
||||
Status::Severity::kHardError},
|
||||
// Errors during Write
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kIOError, Status::SubCode::kNoSpace,
|
||||
false),
|
||||
Status::Severity::kFatalError},
|
||||
};
|
||||
|
||||
std::map<std::tuple<BackgroundErrorReason, Status::Code, bool>, Status::Severity>
|
||||
DefaultErrorSeverityMap = {
|
||||
// Errors during BG compaction
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction,
|
||||
Status::Code::kCorruption, true),
|
||||
Status::Severity::kUnrecoverableError},
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction,
|
||||
Status::Code::kCorruption, false),
|
||||
Status::Severity::kNoError},
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction,
|
||||
Status::Code::kIOError, true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction,
|
||||
Status::Code::kIOError, false),
|
||||
Status::Severity::kNoError},
|
||||
// Errors during BG flush
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush,
|
||||
Status::Code::kCorruption, true),
|
||||
Status::Severity::kUnrecoverableError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush,
|
||||
Status::Code::kCorruption, false),
|
||||
Status::Severity::kNoError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush,
|
||||
Status::Code::kIOError, true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush,
|
||||
Status::Code::kIOError, false),
|
||||
Status::Severity::kNoError},
|
||||
// Errors during Write
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kCorruption, true),
|
||||
Status::Severity::kUnrecoverableError},
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kCorruption, false),
|
||||
Status::Severity::kNoError},
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kIOError, true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback,
|
||||
Status::Code::kIOError, false),
|
||||
Status::Severity::kNoError},
|
||||
};
|
||||
|
||||
std::map<std::tuple<BackgroundErrorReason, bool>, Status::Severity>
|
||||
DefaultReasonMap = {
|
||||
// Errors during BG compaction
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction, true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kCompaction, false),
|
||||
Status::Severity::kNoError},
|
||||
// Errors during BG flush
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kFlush, false),
|
||||
Status::Severity::kNoError},
|
||||
// Errors during Write
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback, true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kWriteCallback, false),
|
||||
Status::Severity::kFatalError},
|
||||
// Errors during Memtable update
|
||||
{std::make_tuple(BackgroundErrorReason::kMemTable, true),
|
||||
Status::Severity::kFatalError},
|
||||
{std::make_tuple(BackgroundErrorReason::kMemTable, false),
|
||||
Status::Severity::kFatalError},
|
||||
};
|
||||
|
||||
Status ErrorHandler::SetBGError(const Status& bg_err, BackgroundErrorReason reason) {
|
||||
db_mutex_->AssertHeld();
|
||||
|
||||
if (bg_err.ok()) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool paranoid = db_options_.paranoid_checks;
|
||||
Status::Severity sev = Status::Severity::kFatalError;
|
||||
Status new_bg_err;
|
||||
bool found = false;
|
||||
|
||||
{
|
||||
auto entry = ErrorSeverityMap.find(std::make_tuple(reason, bg_err.code(),
|
||||
bg_err.subcode(), paranoid));
|
||||
if (entry != ErrorSeverityMap.end()) {
|
||||
sev = entry->second;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
auto entry = DefaultErrorSeverityMap.find(std::make_tuple(reason,
|
||||
bg_err.code(), paranoid));
|
||||
if (entry != DefaultErrorSeverityMap.end()) {
|
||||
sev = entry->second;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
auto entry = DefaultReasonMap.find(std::make_tuple(reason, paranoid));
|
||||
if (entry != DefaultReasonMap.end()) {
|
||||
sev = entry->second;
|
||||
}
|
||||
}
|
||||
|
||||
new_bg_err = Status(bg_err, sev);
|
||||
if (!new_bg_err.ok()) {
|
||||
Status s = new_bg_err;
|
||||
EventHelpers::NotifyOnBackgroundError(db_options_.listeners, reason, &s, db_mutex_);
|
||||
if (!s.ok() && (s.severity() > bg_error_.severity())) {
|
||||
bg_error_ = s;
|
||||
}
|
||||
}
|
||||
|
||||
return bg_error_;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2018-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 "monitoring/instrumented_mutex.h"
|
||||
#include "options/db_options.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class ErrorHandler {
|
||||
public:
|
||||
ErrorHandler(const ImmutableDBOptions& db_options,
|
||||
InstrumentedMutex* db_mutex)
|
||||
: db_options_(db_options),
|
||||
bg_error_(Status::OK()),
|
||||
db_mutex_(db_mutex)
|
||||
{}
|
||||
~ErrorHandler() {}
|
||||
|
||||
Status::Severity GetErrorSeverity(BackgroundErrorReason reason,
|
||||
Status::Code code, Status::SubCode subcode);
|
||||
|
||||
Status SetBGError(const Status& bg_err, BackgroundErrorReason reason);
|
||||
|
||||
Status GetBGError()
|
||||
{
|
||||
return bg_error_;
|
||||
}
|
||||
|
||||
void ClearBGError() {
|
||||
bg_error_ = Status::OK();
|
||||
}
|
||||
|
||||
bool IsDBStopped() {
|
||||
return !bg_error_.ok();
|
||||
}
|
||||
|
||||
bool IsBGWorkStopped() {
|
||||
return !bg_error_.ok();
|
||||
}
|
||||
|
||||
private:
|
||||
const ImmutableDBOptions& db_options_;
|
||||
Status bg_error_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// 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 "util/fault_injection_test_env.h"
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "util/sync_point.h"
|
||||
#endif
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBErrorHandlingTest : public DBTestBase {
|
||||
public:
|
||||
DBErrorHandlingTest() : DBTestBase("/db_error_handling_test") {}
|
||||
};
|
||||
|
||||
class DBErrorHandlingEnv : public EnvWrapper {
|
||||
public:
|
||||
DBErrorHandlingEnv() : EnvWrapper(Env::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;
|
||||
};
|
||||
|
||||
TEST_F(DBErrorHandlingTest, FLushWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = fault_env.get();
|
||||
Status s;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put(Key(0), "va;");
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::Start", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
s = Flush();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kSoftError);
|
||||
fault_env->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBErrorHandlingTest, CompactionWriteError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.env = fault_env.get();
|
||||
Status s;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put(Key(0), "va;");
|
||||
Put(Key(2), "va;");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"FlushMemTableFinished", "BackgroundCallCompaction:0"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::NoSpace("Out of space"));
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put(Key(1), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kSoftError);
|
||||
|
||||
fault_env->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()));
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.env = fault_env.get();
|
||||
Status s;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put(Key(0), "va;");
|
||||
Put(Key(2), "va;");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"FlushMemTableFinished", "BackgroundCallCompaction:0"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BackgroundCallCompaction:0", [&](void *) {
|
||||
fault_env->SetFilesystemActive(false, Status::Corruption("Corruption"));
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put(Key(1), "val");
|
||||
s = Flush();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(s.severity(), rocksdb::Status::Severity::kUnrecoverableError);
|
||||
|
||||
fault_env->SetFilesystemActive(true);
|
||||
s = dbfull()->Resume();
|
||||
ASSERT_NE(s, Status::OK());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -39,7 +39,8 @@ class ExternalSSTFileBasicTest : public DBTestBase {
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys,
|
||||
const std::vector<ValueType>& value_types, int file_id,
|
||||
const std::vector<ValueType>& value_types,
|
||||
std::vector<std::pair<int, int>> range_deletions, int file_id,
|
||||
std::map<std::string, std::string>* true_data) {
|
||||
assert(value_types.size() == 1 || keys.size() == value_types.size());
|
||||
std::string file_path = sst_files_dir_ + ToString(file_id);
|
||||
@@ -49,6 +50,29 @@ class ExternalSSTFileBasicTest : public DBTestBase {
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
for (size_t i = 0; i < range_deletions.size(); i++) {
|
||||
// Account for the effect of range deletions on true_data before
|
||||
// all point operators, even though sst_file_writer.DeleteRange
|
||||
// must be called before other sst_file_writer methods. This is
|
||||
// because point writes take precedence over range deletions
|
||||
// in the same ingested sst.
|
||||
std::string start_key = Key(range_deletions[i].first);
|
||||
std::string end_key = Key(range_deletions[i].second);
|
||||
s = sst_file_writer.DeleteRange(start_key, end_key);
|
||||
if (!s.ok()) {
|
||||
sst_file_writer.Finish();
|
||||
return s;
|
||||
}
|
||||
auto start_key_it = true_data->find(start_key);
|
||||
if (start_key_it == true_data->end()) {
|
||||
start_key_it = true_data->upper_bound(start_key);
|
||||
}
|
||||
auto end_key_it = true_data->find(end_key);
|
||||
if (end_key_it == true_data->end()) {
|
||||
end_key_it = true_data->upper_bound(end_key);
|
||||
}
|
||||
true_data->erase(start_key_it, end_key_it);
|
||||
}
|
||||
for (size_t i = 0; i < keys.size(); i++) {
|
||||
std::string key = Key(keys[i]);
|
||||
std::string value = Key(keys[i]) + ToString(file_id);
|
||||
@@ -86,6 +110,14 @@ class ExternalSSTFileBasicTest : public DBTestBase {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys,
|
||||
const std::vector<ValueType>& value_types, int file_id,
|
||||
std::map<std::string, std::string>* true_data) {
|
||||
return GenerateAndAddExternalFile(options, keys, value_types, {}, file_id,
|
||||
true_data);
|
||||
}
|
||||
|
||||
Status GenerateAndAddExternalFile(
|
||||
const Options options, std::vector<int> keys, const ValueType value_type,
|
||||
int file_id, std::map<std::string, std::string>* true_data) {
|
||||
@@ -126,9 +158,14 @@ TEST_F(ExternalSSTFileBasicTest, Basic) {
|
||||
ASSERT_EQ(file1_info.num_entries, 100);
|
||||
ASSERT_EQ(file1_info.smallest_key, Key(0));
|
||||
ASSERT_EQ(file1_info.largest_key, Key(99));
|
||||
ASSERT_EQ(file1_info.num_range_del_entries, 0);
|
||||
ASSERT_EQ(file1_info.smallest_range_del_key, "");
|
||||
ASSERT_EQ(file1_info.largest_range_del_key, "");
|
||||
// sst_file_writer already finished, cannot add this value
|
||||
s = sst_file_writer.Put(Key(100), "bad_val");
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
s = sst_file_writer.DeleteRange(Key(100), Key(200));
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
// Add file using file path
|
||||
@@ -189,6 +226,7 @@ TEST_F(ExternalSSTFileBasicTest, NoCopy) {
|
||||
ASSERT_EQ(file3_info.num_entries, 15);
|
||||
ASSERT_EQ(file3_info.smallest_key, Key(110));
|
||||
ASSERT_EQ(file3_info.largest_key, Key(124));
|
||||
|
||||
s = DeprecatedAddFile({file1}, true /* move file */);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(Status::NotFound(), env_->FileExists(file1));
|
||||
@@ -197,8 +235,8 @@ TEST_F(ExternalSSTFileBasicTest, NoCopy) {
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file2));
|
||||
|
||||
// This file have overlapping values with the existing data
|
||||
s = DeprecatedAddFile({file2}, true /* move file */);
|
||||
// This file has overlapping values with the existing data
|
||||
s = DeprecatedAddFile({file3}, true /* move file */);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
ASSERT_OK(env_->FileExists(file3));
|
||||
|
||||
@@ -218,34 +256,33 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) {
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2, 3, 4, 5, 6},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
&true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {10, 11, 12, 13},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
|
||||
&true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 4, 6}, ValueType::kTypeValue, file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {11, 15, 19}, ValueType::kTypeValue, file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {120, 130}, ValueType::kTypeValue, file_id++, &true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 130}, ValueType::kTypeValue, file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
|
||||
|
||||
// Write some keys through normal write path
|
||||
@@ -257,17 +294,17 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) {
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {60, 61, 62}, ValueType::kTypeValue, file_id++, &true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {40, 41, 42}, ValueType::kTypeValue, file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {20, 30, 40}, ValueType::kTypeValue, file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2);
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
@@ -314,37 +351,54 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) {
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {1, 2, 3, 4, 5, 6},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
&true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {10, 11, 12, 13},
|
||||
ValueType::kTypeValue, file_id++,
|
||||
|
||||
&true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 4, 6}, ValueType::kTypeMerge, file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {11, 15, 19},
|
||||
ValueType::kTypeDeletion, file_id++,
|
||||
&true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {120, 130}, ValueType::kTypeMerge, file_id++, &true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 130}, ValueType::kTypeDeletion, file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {120},
|
||||
{ValueType::kTypeValue}, {{120, 135}},
|
||||
file_id++, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {}, {}, {{110, 120}},
|
||||
file_id++, &true_data));
|
||||
// The range deletion ends on a key, but it doesn't actually delete
|
||||
// this key because the largest key in the range is exclusive. Still,
|
||||
// it counts as an overlap so a new seqno will be assigned.
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {}, {}, {{100, 109}},
|
||||
file_id++, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5);
|
||||
|
||||
// Write some keys through normal write path
|
||||
for (int i = 0; i < 50; i++) {
|
||||
ASSERT_OK(Put(Key(i), "memtable"));
|
||||
@@ -354,18 +408,18 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) {
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {60, 61, 62}, ValueType::kTypeValue, file_id++, &true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {40, 41, 42}, ValueType::kTypeMerge, file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {20, 30, 40},
|
||||
ValueType::kTypeDeletion, file_id++,
|
||||
&true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2);
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
@@ -414,7 +468,7 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue,
|
||||
ValueType::kTypeMerge, ValueType::kTypeValue, ValueType::kTypeMerge},
|
||||
file_id++, &true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
@@ -422,35 +476,58 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue,
|
||||
ValueType::kTypeMerge},
|
||||
file_id++, &true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 4, 6}, {ValueType::kTypeDeletion, ValueType::kTypeValue,
|
||||
ValueType::kTypeMerge},
|
||||
file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {11, 15, 19}, {ValueType::kTypeDeletion, ValueType::kTypeMerge,
|
||||
ValueType::kTypeValue},
|
||||
file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {120, 130}, {ValueType::kTypeValue, ValueType::kTypeMerge},
|
||||
file_id++, &true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {1, 130}, {ValueType::kTypeMerge, ValueType::kTypeDeletion},
|
||||
file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {150, 151, 152},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge,
|
||||
ValueType::kTypeDeletion},
|
||||
{{150, 160}, {180, 190}}, file_id++, &true_data));
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {150, 151, 152},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue},
|
||||
{{200, 250}}, file_id++, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {300, 301, 302},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge,
|
||||
ValueType::kTypeDeletion},
|
||||
{{1, 2}, {152, 154}}, file_id++, &true_data));
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5);
|
||||
|
||||
// Write some keys through normal write path
|
||||
for (int i = 0; i < 50; i++) {
|
||||
ASSERT_OK(Put(Key(i), "memtable"));
|
||||
@@ -462,14 +539,15 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
options, {60, 61, 62},
|
||||
{ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue},
|
||||
file_id++, &true_data));
|
||||
// File dont overwrite any keys, No seqno needed
|
||||
// File doesn't overwrite any keys, no seqno needed
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {40, 41, 42}, {ValueType::kTypeValue, ValueType::kTypeDeletion,
|
||||
ValueType::kTypeDeletion},
|
||||
options, {40, 41, 42},
|
||||
{ValueType::kTypeValue, ValueType::kTypeDeletion,
|
||||
ValueType::kTypeDeletion},
|
||||
file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1);
|
||||
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
@@ -477,7 +555,7 @@ TEST_F(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
|
||||
{ValueType::kTypeDeletion, ValueType::kTypeDeletion,
|
||||
ValueType::kTypeDeletion},
|
||||
file_id++, &true_data));
|
||||
// File overwrite some keys, a seqno will be assigned
|
||||
// File overwrites some keys, a seqno will be assigned
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2);
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
@@ -589,7 +667,7 @@ TEST_F(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber();
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {60, 90}, {ValueType::kTypeValue, ValueType::kTypeValue},
|
||||
file_id++, &true_data));
|
||||
{{65, 70}, {70, 85}}, file_id++, &true_data));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
@@ -605,6 +683,16 @@ TEST_F(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1));
|
||||
|
||||
// overlaps with L5 file but not memtable or L0 file, so flush is skipped and
|
||||
// file is ingested into L4
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, {}, {}, {{5, 15}}, file_id++,
|
||||
&true_data));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 2));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1));
|
||||
|
||||
// ingested file overlaps with memtable, so flush is triggered before the file
|
||||
// is ingested such that the ingested data is considered newest. So L0 file
|
||||
// count increases by two.
|
||||
@@ -623,7 +711,7 @@ TEST_F(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
|
||||
// seqnum.
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, {151, 175}, {ValueType::kTypeValue, ValueType::kTypeValue},
|
||||
file_id++, &true_data));
|
||||
{{160, 200}}, file_id++, &true_data));
|
||||
ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
|
||||
|
||||
@@ -78,7 +78,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
}
|
||||
|
||||
for (IngestedFileInfo& f : files_to_ingest_) {
|
||||
if (f.num_entries == 0) {
|
||||
if (f.num_entries == 0 && f.num_range_deletions == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
|
||||
@@ -336,12 +336,14 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
|
||||
// Set the global sequence number
|
||||
file_to_ingest->original_seqno = DecodeFixed64(seqno_iter->second.c_str());
|
||||
file_to_ingest->global_seqno_offset = props->properties_offsets.at(
|
||||
auto offsets_iter = props->properties_offsets.find(
|
||||
ExternalSstFilePropertyNames::kGlobalSeqno);
|
||||
|
||||
if (file_to_ingest->global_seqno_offset == 0) {
|
||||
if (offsets_iter == props->properties_offsets.end() ||
|
||||
offsets_iter->second == 0) {
|
||||
file_to_ingest->global_seqno_offset = 0;
|
||||
return Status::Corruption("Was not able to find file global seqno field");
|
||||
}
|
||||
file_to_ingest->global_seqno_offset = offsets_iter->second;
|
||||
} else if (file_to_ingest->version == 1) {
|
||||
// SST file V1 should not have global seqno field
|
||||
assert(seqno_iter == uprops.end());
|
||||
@@ -356,6 +358,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
}
|
||||
// Get number of entries in table
|
||||
file_to_ingest->num_entries = props->num_entries;
|
||||
file_to_ingest->num_range_deletions = props->num_range_deletions;
|
||||
|
||||
ParsedInternalKey key;
|
||||
ReadOptions ro;
|
||||
@@ -367,26 +370,55 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
ro.fill_cache = false;
|
||||
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
|
||||
ro, sv->mutable_cf_options.prefix_extractor.get()));
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
|
||||
// Get first (smallest) key from file
|
||||
// Get first (smallest) and last (largest) key from file.
|
||||
bool bounds_set = false;
|
||||
iter->SeekToFirst();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("external file have non zero sequence number");
|
||||
}
|
||||
file_to_ingest->smallest_user_key = key.user_key.ToString();
|
||||
if (iter->Valid()) {
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("external file have non zero sequence number");
|
||||
}
|
||||
file_to_ingest->smallest_user_key = key.user_key.ToString();
|
||||
|
||||
// Get last (largest) key from file
|
||||
iter->SeekToLast();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
iter->SeekToLast();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("external file have non zero sequence number");
|
||||
}
|
||||
file_to_ingest->largest_user_key = key.user_key.ToString();
|
||||
|
||||
bounds_set = true;
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("external file have non zero sequence number");
|
||||
|
||||
// We may need to adjust these key bounds, depending on whether any range
|
||||
// deletion tombstones extend past them.
|
||||
const Comparator* ucmp = cfd_->internal_comparator().user_comparator();
|
||||
if (range_del_iter != nullptr) {
|
||||
for (range_del_iter->SeekToFirst(); range_del_iter->Valid();
|
||||
range_del_iter->Next()) {
|
||||
if (!ParseInternalKey(range_del_iter->key(), &key)) {
|
||||
return Status::Corruption("external file have corrupted keys");
|
||||
}
|
||||
RangeTombstone tombstone(key, range_del_iter->value());
|
||||
|
||||
if (!bounds_set || ucmp->Compare(tombstone.start_key_,
|
||||
file_to_ingest->smallest_user_key) < 0) {
|
||||
file_to_ingest->smallest_user_key = tombstone.start_key_.ToString();
|
||||
}
|
||||
if (!bounds_set || ucmp->Compare(tombstone.end_key_,
|
||||
file_to_ingest->largest_user_key) > 0) {
|
||||
file_to_ingest->largest_user_key = tombstone.end_key_.ToString();
|
||||
}
|
||||
bounds_set = true;
|
||||
}
|
||||
}
|
||||
file_to_ingest->largest_user_key = key.user_key.ToString();
|
||||
|
||||
file_to_ingest->cf_id = static_cast<uint32_t>(props->column_family_id);
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ struct IngestedFileInfo {
|
||||
uint64_t file_size;
|
||||
// total number of keys in external file
|
||||
uint64_t num_entries;
|
||||
// total number of range deletions in external file
|
||||
uint64_t num_range_deletions;
|
||||
// Id of column family this file shoule be ingested into
|
||||
uint32_t cf_id;
|
||||
// TableProperties read from external file
|
||||
|
||||
+128
-12
@@ -222,6 +222,9 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
ASSERT_EQ(file1_info.num_entries, 100);
|
||||
ASSERT_EQ(file1_info.smallest_key, Key(0));
|
||||
ASSERT_EQ(file1_info.largest_key, Key(99));
|
||||
ASSERT_EQ(file1_info.num_range_del_entries, 0);
|
||||
ASSERT_EQ(file1_info.smallest_range_del_key, "");
|
||||
ASSERT_EQ(file1_info.largest_range_del_key, "");
|
||||
// sst_file_writer already finished, cannot add this value
|
||||
s = sst_file_writer.Put(Key(100), "bad_val");
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
@@ -290,6 +293,58 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
ASSERT_EQ(file5_info.smallest_key, Key(400));
|
||||
ASSERT_EQ(file5_info.largest_key, Key(499));
|
||||
|
||||
// file6.sst (delete 400 => 500)
|
||||
std::string file6 = sst_files_dir_ + "file6.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file6));
|
||||
sst_file_writer.DeleteRange(Key(400), Key(500));
|
||||
ExternalSstFileInfo file6_info;
|
||||
s = sst_file_writer.Finish(&file6_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file6_info.file_path, file6);
|
||||
ASSERT_EQ(file6_info.num_entries, 0);
|
||||
ASSERT_EQ(file6_info.smallest_key, "");
|
||||
ASSERT_EQ(file6_info.largest_key, "");
|
||||
ASSERT_EQ(file6_info.num_range_del_entries, 1);
|
||||
ASSERT_EQ(file6_info.smallest_range_del_key, Key(400));
|
||||
ASSERT_EQ(file6_info.largest_range_del_key, Key(500));
|
||||
|
||||
// file7.sst (delete 500 => 570, put 520 => 599 divisible by 2)
|
||||
std::string file7 = sst_files_dir_ + "file7.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file7));
|
||||
sst_file_writer.DeleteRange(Key(500), Key(550));
|
||||
for (int k = 520; k < 560; k += 2) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val"));
|
||||
}
|
||||
sst_file_writer.DeleteRange(Key(525), Key(575));
|
||||
for (int k = 560; k < 600; k += 2) {
|
||||
ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val"));
|
||||
}
|
||||
ExternalSstFileInfo file7_info;
|
||||
s = sst_file_writer.Finish(&file7_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file7_info.file_path, file7);
|
||||
ASSERT_EQ(file7_info.num_entries, 40);
|
||||
ASSERT_EQ(file7_info.smallest_key, Key(520));
|
||||
ASSERT_EQ(file7_info.largest_key, Key(598));
|
||||
ASSERT_EQ(file7_info.num_range_del_entries, 2);
|
||||
ASSERT_EQ(file7_info.smallest_range_del_key, Key(500));
|
||||
ASSERT_EQ(file7_info.largest_range_del_key, Key(575));
|
||||
|
||||
// file8.sst (delete 600 => 700)
|
||||
std::string file8 = sst_files_dir_ + "file8.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file8));
|
||||
sst_file_writer.DeleteRange(Key(600), Key(700));
|
||||
ExternalSstFileInfo file8_info;
|
||||
s = sst_file_writer.Finish(&file8_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file8_info.file_path, file8);
|
||||
ASSERT_EQ(file8_info.num_entries, 0);
|
||||
ASSERT_EQ(file8_info.smallest_key, "");
|
||||
ASSERT_EQ(file8_info.largest_key, "");
|
||||
ASSERT_EQ(file8_info.num_range_del_entries, 1);
|
||||
ASSERT_EQ(file8_info.smallest_range_del_key, Key(600));
|
||||
ASSERT_EQ(file8_info.largest_range_del_key, Key(700));
|
||||
|
||||
// Cannot create an empty sst file
|
||||
std::string file_empty = sst_files_dir_ + "file_empty.sst";
|
||||
ExternalSstFileInfo file_empty_info;
|
||||
@@ -336,6 +391,16 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
// Key range of file5 (400 => 499) dont 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
|
||||
ASSERT_OK(DeprecatedAddFile({file7}));
|
||||
|
||||
// Key range of file7 (600 => 700) dont overlap with any keys in DB
|
||||
ASSERT_OK(DeprecatedAddFile({file8}));
|
||||
|
||||
// Make sure values are correct before and after flush/compaction
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (int k = 0; k < 200; k++) {
|
||||
@@ -349,6 +414,13 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
std::string value = Key(k) + "_val";
|
||||
ASSERT_EQ(Get(Key(k)), value);
|
||||
}
|
||||
for (int k = 500; k < 600; k++) {
|
||||
std::string value = Key(k) + "_val";
|
||||
if (k < 520 || k % 2 == 1) {
|
||||
value = "NOT_FOUND";
|
||||
}
|
||||
ASSERT_EQ(Get(Key(k)), value);
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
}
|
||||
@@ -377,7 +449,8 @@ TEST_F(ExternalSSTFileTest, Basic) {
|
||||
ASSERT_EQ(Get(Key(k)), value);
|
||||
}
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction |
|
||||
kRangeDelSkipConfigs));
|
||||
}
|
||||
class SstFileWriterCollector : public TablePropertiesCollector {
|
||||
public:
|
||||
@@ -518,17 +591,57 @@ TEST_F(ExternalSSTFileTest, AddList) {
|
||||
ASSERT_EQ(file5_info.smallest_key, Key(200));
|
||||
ASSERT_EQ(file5_info.largest_key, Key(299));
|
||||
|
||||
// file6.sst (delete 0 => 100)
|
||||
std::string file6 = sst_files_dir_ + "file6.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file6));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange(Key(0), Key(75)));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange(Key(25), Key(100)));
|
||||
ExternalSstFileInfo file6_info;
|
||||
s = sst_file_writer.Finish(&file6_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file6_info.file_path, file6);
|
||||
ASSERT_EQ(file6_info.num_entries, 0);
|
||||
ASSERT_EQ(file6_info.smallest_key, "");
|
||||
ASSERT_EQ(file6_info.largest_key, "");
|
||||
ASSERT_EQ(file6_info.num_range_del_entries, 2);
|
||||
ASSERT_EQ(file6_info.smallest_range_del_key, Key(0));
|
||||
ASSERT_EQ(file6_info.largest_range_del_key, Key(100));
|
||||
|
||||
// file7.sst (delete 100 => 200)
|
||||
std::string file7 = sst_files_dir_ + "file7.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file7));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange(Key(100), Key(200)));
|
||||
ExternalSstFileInfo file7_info;
|
||||
s = sst_file_writer.Finish(&file7_info);
|
||||
ASSERT_TRUE(s.ok()) << s.ToString();
|
||||
ASSERT_EQ(file7_info.file_path, file7);
|
||||
ASSERT_EQ(file7_info.num_entries, 0);
|
||||
ASSERT_EQ(file7_info.smallest_key, "");
|
||||
ASSERT_EQ(file7_info.largest_key, "");
|
||||
ASSERT_EQ(file7_info.num_range_del_entries, 1);
|
||||
ASSERT_EQ(file7_info.smallest_range_del_key, Key(100));
|
||||
ASSERT_EQ(file7_info.largest_range_del_key, Key(200));
|
||||
|
||||
// list 1 has internal key range conflict
|
||||
std::vector<std::string> file_list0({file1, file2});
|
||||
std::vector<std::string> file_list1({file3, file2, file1});
|
||||
std::vector<std::string> file_list2({file5});
|
||||
std::vector<std::string> file_list3({file3, file4});
|
||||
std::vector<std::string> file_list4({file5, file7});
|
||||
std::vector<std::string> file_list5({file6, file7});
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// This list of files have key ranges are overlapping with each other
|
||||
// These lists of files have key ranges that overlap with each other
|
||||
s = DeprecatedAddFile(file_list1);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
// Both of the following overlap on the end key of a range deletion
|
||||
// tombstone. This is a limitation because these tombstones have exclusive
|
||||
// end keys that should not count as overlapping with other keys.
|
||||
s = DeprecatedAddFile(file_list4);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
s = DeprecatedAddFile(file_list5);
|
||||
ASSERT_FALSE(s.ok()) << s.ToString();
|
||||
|
||||
// Add files using file path list
|
||||
s = DeprecatedAddFile(file_list0);
|
||||
@@ -619,7 +732,8 @@ TEST_F(ExternalSSTFileTest, AddList) {
|
||||
ASSERT_EQ(Get(Key(k)), value);
|
||||
}
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction |
|
||||
kRangeDelSkipConfigs));
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, AddListAtomicity) {
|
||||
@@ -897,11 +1011,11 @@ TEST_F(ExternalSSTFileTest, MultiThreaded) {
|
||||
|
||||
TEST_F(ExternalSSTFileTest, OverlappingRanges) {
|
||||
Random rnd(301);
|
||||
int picked_level = 0;
|
||||
SequenceNumber assigned_seqno = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"ExternalSstFileIngestionJob::Run", [&picked_level](void* arg) {
|
||||
"ExternalSstFileIngestionJob::Run", [&assigned_seqno](void* arg) {
|
||||
ASSERT_TRUE(arg != nullptr);
|
||||
picked_level = *(static_cast<int*>(arg));
|
||||
assigned_seqno = *(static_cast<SequenceNumber*>(arg));
|
||||
});
|
||||
bool need_flush = false;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -925,7 +1039,7 @@ TEST_F(ExternalSSTFileTest, OverlappingRanges) {
|
||||
|
||||
printf("Option config = %d\n", option_config_);
|
||||
std::vector<std::pair<int, int>> key_ranges;
|
||||
for (int i = 0; i < 500; i++) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int range_start = rnd.Uniform(20000);
|
||||
int keys_per_range = 10 + rnd.Uniform(41);
|
||||
|
||||
@@ -971,7 +1085,8 @@ TEST_F(ExternalSSTFileTest, OverlappingRanges) {
|
||||
s = DeprecatedAddFile({file_name});
|
||||
auto it = true_data.lower_bound(Key(range_start));
|
||||
if (option_config_ != kUniversalCompaction &&
|
||||
option_config_ != kUniversalCompactionMultiLevel) {
|
||||
option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
if (it != true_data.end() && it->first <= Key(range_end)) {
|
||||
// This range overlap with data already exist in DB
|
||||
ASSERT_NOK(s);
|
||||
@@ -982,7 +1097,7 @@ TEST_F(ExternalSSTFileTest, OverlappingRanges) {
|
||||
}
|
||||
} else {
|
||||
if ((it != true_data.end() && it->first <= Key(range_end)) ||
|
||||
need_flush || picked_level > 0 || overlap_with_db) {
|
||||
need_flush || assigned_seqno > 0 || overlap_with_db) {
|
||||
// This range overlap with data already exist in DB
|
||||
ASSERT_NOK(s);
|
||||
failed_add_file++;
|
||||
@@ -1194,8 +1309,9 @@ TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) {
|
||||
ASSERT_OK(GenerateAndAddExternalFile(options, file_keys, range_id));
|
||||
};
|
||||
|
||||
const int num_of_ranges = 1000;
|
||||
std::vector<port::Thread> threads;
|
||||
while (range_id < 5000) {
|
||||
while (range_id < num_of_ranges) {
|
||||
int range_start = range_id * 10;
|
||||
int range_end = range_start + 10;
|
||||
|
||||
@@ -1220,7 +1336,7 @@ TEST_F(ExternalSSTFileTest, CompactDuringAddFileRandom) {
|
||||
range_id++;
|
||||
}
|
||||
|
||||
for (int rid = 0; rid < 5000; rid++) {
|
||||
for (int rid = 0; rid < num_of_ranges; rid++) {
|
||||
int range_start = rid * 10;
|
||||
int range_end = range_start + 10;
|
||||
|
||||
@@ -1475,7 +1591,7 @@ TEST_F(ExternalSSTFileTest, IngestFileWithGlobalSeqnoRandomized) {
|
||||
|
||||
Random rnd(301);
|
||||
std::map<std::string, std::string> true_data;
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
for (int i = 0; i < 500; i++) {
|
||||
std::vector<std::pair<std::string, std::string>> random_data;
|
||||
for (int j = 0; j < 100; j++) {
|
||||
std::string k;
|
||||
|
||||
@@ -107,18 +107,18 @@ class FaultInjectionTest
|
||||
Options options;
|
||||
switch (option_config_) {
|
||||
case kWalDir:
|
||||
options.wal_dir = test::TmpDir(env_) + "/fault_test_wal";
|
||||
options.wal_dir = test::PerThreadDBPath(env_, "fault_test_wal");
|
||||
break;
|
||||
case kDifferentDataDir:
|
||||
options.db_paths.emplace_back(test::TmpDir(env_) + "/fault_test_data",
|
||||
1000000U);
|
||||
options.db_paths.emplace_back(
|
||||
test::PerThreadDBPath(env_, "fault_test_data"), 1000000U);
|
||||
break;
|
||||
case kSyncWal:
|
||||
sync_use_wal_ = true;
|
||||
sync_use_compact_ = false;
|
||||
break;
|
||||
case kWalDirSyncWal:
|
||||
options.wal_dir = test::TmpDir(env_) + "/fault_test_wal";
|
||||
options.wal_dir = test::PerThreadDBPath(env_, "/fault_test_wal");
|
||||
sync_use_wal_ = true;
|
||||
sync_use_compact_ = false;
|
||||
break;
|
||||
@@ -158,7 +158,7 @@ class FaultInjectionTest
|
||||
table_options.block_cache = tiny_cache_;
|
||||
options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
dbname_ = test::TmpDir() + "/fault_test";
|
||||
dbname_ = test::PerThreadDBPath("fault_test");
|
||||
|
||||
EXPECT_OK(DestroyDB(dbname_, options_));
|
||||
|
||||
|
||||
@@ -187,6 +187,7 @@ void FlushJob::PickMemTable() {
|
||||
|
||||
Status FlushJob::Run(LogsWithPrepTracker* prep_tracker,
|
||||
FileMetaData* file_meta) {
|
||||
TEST_SYNC_POINT("FlushJob::Start");
|
||||
db_mutex_->AssertHeld();
|
||||
assert(pick_memtable_called);
|
||||
AutoThreadOperationStageUpdater stage_run(
|
||||
|
||||
@@ -27,7 +27,7 @@ class FlushJobTest : public testing::Test {
|
||||
public:
|
||||
FlushJobTest()
|
||||
: env_(Env::Default()),
|
||||
dbname_(test::TmpDir() + "/flush_job_test"),
|
||||
dbname_(test::PerThreadDBPath("flush_job_test")),
|
||||
options_(),
|
||||
db_options_(options_),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
|
||||
@@ -75,7 +75,7 @@ class ForwardLevelIterator : public InternalIterator {
|
||||
cfd_->internal_comparator(), {} /* snapshots */);
|
||||
file_iter_ = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
files_[file_index_]->fd,
|
||||
*files_[file_index_],
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
prefix_extractor_, nullptr /* table_reader_ptr */, nullptr, false);
|
||||
file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
@@ -635,7 +635,7 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
continue;
|
||||
}
|
||||
l0_iters_.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), l0->fd,
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(), *l0,
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
sv_->mutable_cf_options.prefix_extractor.get()));
|
||||
}
|
||||
@@ -706,7 +706,7 @@ void ForwardIterator::RenewIterators() {
|
||||
}
|
||||
l0_iters_new.push_back(cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
l0_files_new[inew]->fd,
|
||||
*l0_files_new[inew],
|
||||
read_options_.ignore_range_deletions ? nullptr : &range_del_agg,
|
||||
svnew->mutable_cf_options.prefix_extractor.get()));
|
||||
}
|
||||
@@ -766,7 +766,7 @@ void ForwardIterator::ResetIncompleteIterators() {
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
l0_files[i]->fd, nullptr /* range_del_agg */,
|
||||
*l0_files[i], nullptr /* range_del_agg */,
|
||||
sv_->mutable_cf_options.prefix_extractor.get());
|
||||
l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
std::mt19937 rng{std::random_device()()};
|
||||
rocksdb::Status status;
|
||||
std::string path = rocksdb::test::TmpDir() + "/forward_iterator_test";
|
||||
std::string path = rocksdb::test::PerThreadDBPath("forward_iterator_test");
|
||||
fprintf(stderr, "db path is %s\n", path.c_str());
|
||||
rocksdb::Options options;
|
||||
options.create_if_missing = true;
|
||||
|
||||
+72
-46
@@ -249,6 +249,7 @@ static const std::string estimate_oldest_key_time = "estimate-oldest-key-time";
|
||||
static const std::string block_cache_capacity = "block-cache-capacity";
|
||||
static const std::string block_cache_usage = "block-cache-usage";
|
||||
static const std::string block_cache_pinned_usage = "block-cache-pinned-usage";
|
||||
static const std::string options_statistics = "options-statistics";
|
||||
|
||||
const std::string DB::Properties::kNumFilesAtLevelPrefix =
|
||||
rocksdb_prefix + num_files_at_level_prefix;
|
||||
@@ -332,116 +333,141 @@ const std::string DB::Properties::kBlockCacheUsage =
|
||||
rocksdb_prefix + block_cache_usage;
|
||||
const std::string DB::Properties::kBlockCachePinnedUsage =
|
||||
rocksdb_prefix + block_cache_pinned_usage;
|
||||
const std::string DB::Properties::kOptionsStatistics =
|
||||
rocksdb_prefix + options_statistics;
|
||||
|
||||
const std::unordered_map<std::string, DBPropertyInfo>
|
||||
InternalStats::ppt_name_to_info = {
|
||||
{DB::Properties::kNumFilesAtLevelPrefix,
|
||||
{false, &InternalStats::HandleNumFilesAtLevel, nullptr, nullptr}},
|
||||
{false, &InternalStats::HandleNumFilesAtLevel, nullptr, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kCompressionRatioAtLevelPrefix,
|
||||
{false, &InternalStats::HandleCompressionRatioAtLevelPrefix, nullptr,
|
||||
nullptr}},
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kLevelStats,
|
||||
{false, &InternalStats::HandleLevelStats, nullptr, nullptr}},
|
||||
{false, &InternalStats::HandleLevelStats, nullptr, nullptr, nullptr}},
|
||||
{DB::Properties::kStats,
|
||||
{false, &InternalStats::HandleStats, nullptr, nullptr}},
|
||||
{false, &InternalStats::HandleStats, nullptr, nullptr, nullptr}},
|
||||
{DB::Properties::kCFStats,
|
||||
{false, &InternalStats::HandleCFStats, nullptr,
|
||||
&InternalStats::HandleCFMapStats}},
|
||||
&InternalStats::HandleCFMapStats, nullptr}},
|
||||
{DB::Properties::kCFStatsNoFileHistogram,
|
||||
{false, &InternalStats::HandleCFStatsNoFileHistogram, nullptr,
|
||||
{false, &InternalStats::HandleCFStatsNoFileHistogram, nullptr, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kCFFileHistogram,
|
||||
{false, &InternalStats::HandleCFFileHistogram, nullptr, nullptr}},
|
||||
{false, &InternalStats::HandleCFFileHistogram, nullptr, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kDBStats,
|
||||
{false, &InternalStats::HandleDBStats, nullptr, nullptr}},
|
||||
{false, &InternalStats::HandleDBStats, nullptr, nullptr, nullptr}},
|
||||
{DB::Properties::kSSTables,
|
||||
{false, &InternalStats::HandleSsTables, nullptr, nullptr}},
|
||||
{false, &InternalStats::HandleSsTables, nullptr, nullptr, nullptr}},
|
||||
{DB::Properties::kAggregatedTableProperties,
|
||||
{false, &InternalStats::HandleAggregatedTableProperties, nullptr,
|
||||
nullptr}},
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kAggregatedTablePropertiesAtLevel,
|
||||
{false, &InternalStats::HandleAggregatedTablePropertiesAtLevel,
|
||||
nullptr, nullptr}},
|
||||
nullptr, nullptr, nullptr}},
|
||||
{DB::Properties::kNumImmutableMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumImmutableMemTable, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleNumImmutableMemTable, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumImmutableMemTableFlushed,
|
||||
{false, nullptr, &InternalStats::HandleNumImmutableMemTableFlushed,
|
||||
nullptr}},
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kMemTableFlushPending,
|
||||
{false, nullptr, &InternalStats::HandleMemTableFlushPending, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleMemTableFlushPending, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kCompactionPending,
|
||||
{false, nullptr, &InternalStats::HandleCompactionPending, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleCompactionPending, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kBackgroundErrors,
|
||||
{false, nullptr, &InternalStats::HandleBackgroundErrors, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleBackgroundErrors, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kCurSizeActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleCurSizeActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleCurSizeActiveMemTable, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kCurSizeAllMemTables,
|
||||
{false, nullptr, &InternalStats::HandleCurSizeAllMemTables, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleCurSizeAllMemTables, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kSizeAllMemTables,
|
||||
{false, nullptr, &InternalStats::HandleSizeAllMemTables, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleSizeAllMemTables, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumEntriesActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumEntriesActiveMemTable,
|
||||
nullptr}},
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kNumEntriesImmMemTables,
|
||||
{false, nullptr, &InternalStats::HandleNumEntriesImmMemTables,
|
||||
{false, nullptr, &InternalStats::HandleNumEntriesImmMemTables, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumDeletesActiveMemTable,
|
||||
{false, nullptr, &InternalStats::HandleNumDeletesActiveMemTable,
|
||||
nullptr}},
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kNumDeletesImmMemTables,
|
||||
{false, nullptr, &InternalStats::HandleNumDeletesImmMemTables,
|
||||
{false, nullptr, &InternalStats::HandleNumDeletesImmMemTables, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kEstimateNumKeys,
|
||||
{false, nullptr, &InternalStats::HandleEstimateNumKeys, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleEstimateNumKeys, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kEstimateTableReadersMem,
|
||||
{true, nullptr, &InternalStats::HandleEstimateTableReadersMem,
|
||||
{true, nullptr, &InternalStats::HandleEstimateTableReadersMem, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kIsFileDeletionsEnabled,
|
||||
{false, nullptr, &InternalStats::HandleIsFileDeletionsEnabled,
|
||||
{false, nullptr, &InternalStats::HandleIsFileDeletionsEnabled, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumSnapshots,
|
||||
{false, nullptr, &InternalStats::HandleNumSnapshots, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleNumSnapshots, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kOldestSnapshotTime,
|
||||
{false, nullptr, &InternalStats::HandleOldestSnapshotTime, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleOldestSnapshotTime, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumLiveVersions,
|
||||
{false, nullptr, &InternalStats::HandleNumLiveVersions, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleNumLiveVersions, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kCurrentSuperVersionNumber,
|
||||
{false, nullptr, &InternalStats::HandleCurrentSuperVersionNumber,
|
||||
nullptr}},
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kEstimateLiveDataSize,
|
||||
{true, nullptr, &InternalStats::HandleEstimateLiveDataSize, nullptr}},
|
||||
{true, nullptr, &InternalStats::HandleEstimateLiveDataSize, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kMinLogNumberToKeep,
|
||||
{false, nullptr, &InternalStats::HandleMinLogNumberToKeep, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleMinLogNumberToKeep, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kBaseLevel,
|
||||
{false, nullptr, &InternalStats::HandleBaseLevel, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleBaseLevel, nullptr, nullptr}},
|
||||
{DB::Properties::kTotalSstFilesSize,
|
||||
{false, nullptr, &InternalStats::HandleTotalSstFilesSize, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleTotalSstFilesSize, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kLiveSstFilesSize,
|
||||
{false, nullptr, &InternalStats::HandleLiveSstFilesSize, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleLiveSstFilesSize, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kEstimatePendingCompactionBytes,
|
||||
{false, nullptr, &InternalStats::HandleEstimatePendingCompactionBytes,
|
||||
nullptr}},
|
||||
nullptr, nullptr}},
|
||||
{DB::Properties::kNumRunningFlushes,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningFlushes, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleNumRunningFlushes, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kNumRunningCompactions,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningCompactions,
|
||||
{false, nullptr, &InternalStats::HandleNumRunningCompactions, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kActualDelayedWriteRate,
|
||||
{false, nullptr, &InternalStats::HandleActualDelayedWriteRate,
|
||||
{false, nullptr, &InternalStats::HandleActualDelayedWriteRate, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kIsWriteStopped,
|
||||
{false, nullptr, &InternalStats::HandleIsWriteStopped, nullptr}},
|
||||
{false, nullptr, &InternalStats::HandleIsWriteStopped, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kEstimateOldestKeyTime,
|
||||
{false, nullptr, &InternalStats::HandleEstimateOldestKeyTime,
|
||||
{false, nullptr, &InternalStats::HandleEstimateOldestKeyTime, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kBlockCacheCapacity,
|
||||
{false, nullptr, &InternalStats::HandleBlockCacheCapacity, nullptr}},
|
||||
{DB::Properties::kBlockCacheUsage,
|
||||
{false, nullptr, &InternalStats::HandleBlockCacheUsage, nullptr}},
|
||||
{DB::Properties::kBlockCachePinnedUsage,
|
||||
{false, nullptr, &InternalStats::HandleBlockCachePinnedUsage,
|
||||
{false, nullptr, &InternalStats::HandleBlockCacheCapacity, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kBlockCacheUsage,
|
||||
{false, nullptr, &InternalStats::HandleBlockCacheUsage, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kBlockCachePinnedUsage,
|
||||
{false, nullptr, &InternalStats::HandleBlockCachePinnedUsage, nullptr,
|
||||
nullptr}},
|
||||
{DB::Properties::kOptionsStatistics,
|
||||
{false, nullptr, nullptr, nullptr,
|
||||
&DBImpl::GetPropertyHandleOptionsStatistics}},
|
||||
};
|
||||
|
||||
const DBPropertyInfo* GetPropertyInfo(const Slice& property) {
|
||||
|
||||
+4
-1
@@ -45,6 +45,10 @@ struct DBPropertyInfo {
|
||||
|
||||
// @param props Map of general properties to populate
|
||||
bool (InternalStats::*handle_map)(std::map<std::string, std::string>* props);
|
||||
|
||||
// handle the string type properties rely on DBImpl methods
|
||||
// @param value Value-result argument for storing the property's string value
|
||||
bool (DBImpl::*handle_string_dbimpl)(std::string* value);
|
||||
};
|
||||
|
||||
extern const DBPropertyInfo* GetPropertyInfo(const Slice& property);
|
||||
@@ -538,7 +542,6 @@ class InternalStats {
|
||||
bool HandleBlockCacheUsage(uint64_t* value, DBImpl* db, Version* version);
|
||||
bool HandleBlockCachePinnedUsage(uint64_t* value, DBImpl* db,
|
||||
Version* version);
|
||||
|
||||
// Total number of background errors encountered. Every time a flush task
|
||||
// or compaction task fails, this counter is incremented. The failure can
|
||||
// be caused by any possible reason, including file system errors, out of
|
||||
|
||||
+3
-2
@@ -14,6 +14,7 @@
|
||||
#include "util/coding.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
namespace log {
|
||||
@@ -151,7 +152,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
// in clean shutdown we don't expect any error in the log files
|
||||
ReportCorruption(drop_size, "truncated header");
|
||||
}
|
||||
// fall-thru
|
||||
FALLTHROUGH_INTENDED;
|
||||
|
||||
case kEof:
|
||||
if (in_fragmented_record) {
|
||||
@@ -181,7 +182,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// fall-thru
|
||||
FALLTHROUGH_INTENDED;
|
||||
|
||||
case kBadRecord:
|
||||
if (in_fragmented_record) {
|
||||
|
||||
+6
-6
@@ -160,8 +160,8 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
: reader_contents_(),
|
||||
dest_holder_(test::GetWritableFileWriter(
|
||||
new test::StringSink(&reader_contents_))),
|
||||
source_holder_(
|
||||
test::GetSequentialFileReader(new StringSource(reader_contents_))),
|
||||
source_holder_(test::GetSequentialFileReader(
|
||||
new StringSource(reader_contents_), "" /* file name */)),
|
||||
writer_(std::move(dest_holder_), 123, GetParam()),
|
||||
reader_(nullptr, std::move(source_holder_), &report_, true /*checksum*/,
|
||||
0 /*initial_offset*/, 123) {
|
||||
@@ -268,8 +268,8 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
|
||||
void CheckOffsetPastEndReturnsNoRecords(uint64_t offset_past_end) {
|
||||
WriteInitialOffsetLog();
|
||||
unique_ptr<SequentialFileReader> file_reader(
|
||||
test::GetSequentialFileReader(new StringSource(reader_contents_)));
|
||||
unique_ptr<SequentialFileReader> file_reader(test::GetSequentialFileReader(
|
||||
new StringSource(reader_contents_), "" /* fname */));
|
||||
unique_ptr<Reader> offset_reader(
|
||||
new Reader(nullptr, std::move(file_reader), &report_,
|
||||
true /*checksum*/, WrittenBytes() + offset_past_end, 123));
|
||||
@@ -281,8 +281,8 @@ class LogTest : public ::testing::TestWithParam<int> {
|
||||
void CheckInitialOffsetRecord(uint64_t initial_offset,
|
||||
int expected_record_offset) {
|
||||
WriteInitialOffsetLog();
|
||||
unique_ptr<SequentialFileReader> file_reader(
|
||||
test::GetSequentialFileReader(new StringSource(reader_contents_)));
|
||||
unique_ptr<SequentialFileReader> file_reader(test::GetSequentialFileReader(
|
||||
new StringSource(reader_contents_), "" /* fname */));
|
||||
unique_ptr<Reader> offset_reader(
|
||||
new Reader(nullptr, std::move(file_reader), &report_,
|
||||
true /*checksum*/, initial_offset, 123));
|
||||
|
||||
@@ -40,7 +40,7 @@ class ManualCompactionTest : public testing::Test {
|
||||
public:
|
||||
ManualCompactionTest() {
|
||||
// Get rid of any state from an old run.
|
||||
dbname_ = rocksdb::test::TmpDir() + "/rocksdb_cbug_test";
|
||||
dbname_ = rocksdb::test::PerThreadDBPath("rocksdb_cbug_test");
|
||||
DestroyDB(dbname_, rocksdb::Options());
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -36,6 +36,7 @@
|
||||
#include "util/memory_usage.h"
|
||||
#include "util/murmurhash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -579,7 +580,7 @@ struct Saver {
|
||||
|
||||
bool CheckCallback(SequenceNumber _seq) {
|
||||
if (callback_) {
|
||||
return callback_->IsCommitted(_seq);
|
||||
return callback_->IsVisible(_seq);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -639,7 +640,7 @@ static bool SaveValue(void* arg, const char* entry) {
|
||||
*(s->found_final_value) = true;
|
||||
return false;
|
||||
}
|
||||
// intentional fallthrough
|
||||
FALLTHROUGH_INTENDED;
|
||||
case kTypeValue: {
|
||||
if (s->inplace_update_support) {
|
||||
s->mem->GetLock(s->key->user_key())->ReadLock();
|
||||
|
||||
+16
-2
@@ -41,7 +41,6 @@ void MemTableListVersion::UnrefMemTable(autovector<MemTable*>* to_delete,
|
||||
to_delete->push_back(m);
|
||||
assert(*parent_memtable_list_memory_usage_ >= m->ApproximateMemoryUsage());
|
||||
*parent_memtable_list_memory_usage_ -= m->ApproximateMemoryUsage();
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +400,22 @@ Status MemTableList::InstallMemtableFlushResults(
|
||||
// All the later memtables that have the same filenum
|
||||
// are part of the same batch. They can be committed now.
|
||||
uint64_t mem_id = 1; // how many memtables have been flushed.
|
||||
if (s.ok()) { // commit new state
|
||||
|
||||
// commit new state only if the column family is NOT dropped.
|
||||
// The reason is as follows (refer to
|
||||
// ColumnFamilyTest.FlushAndDropRaceCondition).
|
||||
// If the column family is dropped, then according to LogAndApply, its
|
||||
// corrresponding flush operation is NOT written to the MANIFEST. This
|
||||
// means the DB is not aware of the L0 files generated from the flush.
|
||||
// By committing the new state, we remove the memtable from the memtable
|
||||
// list. Creating an iterator on this column family will not be able to
|
||||
// read full data since the memtable is removed, and the DB is not aware
|
||||
// of the L0 files, causing MergingIterator unable to build child
|
||||
// iterators. RocksDB contract requires that the iterator can be created
|
||||
// on a dropped column family, and we must be able to
|
||||
// read full data as long as column family handle is not deleted, even if
|
||||
// the column family is dropped.
|
||||
if (s.ok() && !cfd->IsDropped()) { // commit new state
|
||||
while (batch_count-- > 0) {
|
||||
MemTable* m = current_->memlist_.back();
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Level-0 commit table #%" PRIu64
|
||||
|
||||
@@ -27,7 +27,7 @@ class MemTableListTest : public testing::Test {
|
||||
Options options;
|
||||
|
||||
MemTableListTest() : db(nullptr) {
|
||||
dbname = test::TmpDir() + "/memtable_list_test";
|
||||
dbname = test::PerThreadDBPath("memtable_list_test");
|
||||
}
|
||||
|
||||
// Create a test db if not yet created
|
||||
|
||||
+3
-3
@@ -14,6 +14,7 @@
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "table/format.h"
|
||||
#include "table/internal_iterator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -236,8 +237,7 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
if (filter != CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
range_del_agg != nullptr &&
|
||||
range_del_agg->ShouldDelete(
|
||||
iter->key(),
|
||||
RangeDelAggregator::RangePositioningMode::kForwardTraversal)) {
|
||||
iter->key(), RangeDelPositioningMode::kForwardTraversal)) {
|
||||
filter = CompactionFilter::Decision::kRemove;
|
||||
}
|
||||
if (filter == CompactionFilter::Decision::kKeep ||
|
||||
@@ -372,7 +372,7 @@ CompactionFilter::Decision MergeHelper::FilterMerge(const Slice& user_key,
|
||||
if (compaction_filter_ == nullptr) {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
if (stats_ != nullptr) {
|
||||
if (stats_ != nullptr && ShouldReportDetailedTime(env_, stats_)) {
|
||||
filter_timer_.Start();
|
||||
}
|
||||
compaction_filter_value_.clear();
|
||||
|
||||
+32
-40
@@ -20,15 +20,17 @@
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
using namespace rocksdb;
|
||||
namespace rocksdb {
|
||||
|
||||
bool use_compression;
|
||||
|
||||
class MergeTest : public testing::Test {};
|
||||
|
||||
namespace {
|
||||
size_t num_merge_operator_calls;
|
||||
void resetNumMergeOperatorCalls() { num_merge_operator_calls = 0; }
|
||||
|
||||
size_t num_partial_merge_calls;
|
||||
void resetNumPartialMergeCalls() { num_partial_merge_calls = 0; }
|
||||
}
|
||||
|
||||
class CountMergeOperator : public AssociativeMergeOperator {
|
||||
public:
|
||||
@@ -74,7 +76,6 @@ class CountMergeOperator : public AssociativeMergeOperator {
|
||||
std::shared_ptr<MergeOperator> mergeOperator_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
std::shared_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
|
||||
const size_t max_successive_merges = 0) {
|
||||
DB* db;
|
||||
@@ -87,7 +88,6 @@ std::shared_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
|
||||
// DBWithTTL is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (ttl) {
|
||||
std::cout << "Opening database with TTL\n";
|
||||
DBWithTTL* db_with_ttl;
|
||||
s = DBWithTTL::Open(options, dbname, &db_with_ttl);
|
||||
db = db_with_ttl;
|
||||
@@ -104,7 +104,6 @@ std::shared_ptr<DB> OpenDb(const std::string& dbname, const bool ttl = false,
|
||||
}
|
||||
return std::shared_ptr<DB>(db);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Imagine we are maintaining a set of uint64 counters.
|
||||
// Each counter has a distinct name. And we would like
|
||||
@@ -246,12 +245,11 @@ class MergeBasedCounters : public Counters {
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
void dumpDb(DB* db) {
|
||||
auto it = unique_ptr<Iterator>(db->NewIterator(ReadOptions()));
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
uint64_t value = DecodeFixed64(it->value().data());
|
||||
std::cout << it->key().ToString() << ": " << value << std::endl;
|
||||
//uint64_t value = DecodeFixed64(it->value().data());
|
||||
//std::cout << it->key().ToString() << ": " << value << std::endl;
|
||||
}
|
||||
assert(it->status().ok()); // Check for any errors found during the scan
|
||||
}
|
||||
@@ -281,8 +279,6 @@ void testCounters(Counters& counters, DB* db, bool test_compaction) {
|
||||
|
||||
dumpDb(db);
|
||||
|
||||
std::cout << "1\n";
|
||||
|
||||
// 1+...+49 = ?
|
||||
uint64_t sum = 0;
|
||||
for (int i = 1; i < 50; i++) {
|
||||
@@ -291,17 +287,12 @@ void testCounters(Counters& counters, DB* db, bool test_compaction) {
|
||||
}
|
||||
assert(counters.assert_get("b") == sum);
|
||||
|
||||
std::cout << "2\n";
|
||||
dumpDb(db);
|
||||
|
||||
std::cout << "3\n";
|
||||
|
||||
if (test_compaction) {
|
||||
db->Flush(o);
|
||||
|
||||
std::cout << "Compaction started ...\n";
|
||||
db->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
std::cout << "Compaction ended\n";
|
||||
|
||||
dumpDb(db);
|
||||
|
||||
@@ -411,44 +402,35 @@ void testSingleBatchSuccessiveMerge(DB* db, size_t max_num_merges,
|
||||
static_cast<size_t>((num_merges % (max_num_merges + 1))));
|
||||
}
|
||||
|
||||
void runTest(int argc, const std::string& dbname, const bool use_ttl = false) {
|
||||
bool compact = false;
|
||||
if (argc > 1) {
|
||||
compact = true;
|
||||
std::cout << "Turn on Compaction\n";
|
||||
}
|
||||
void runTest(const std::string& dbname, const bool use_ttl = false) {
|
||||
|
||||
{
|
||||
auto db = OpenDb(dbname, use_ttl);
|
||||
|
||||
{
|
||||
std::cout << "Test read-modify-write counters... \n";
|
||||
Counters counters(db, 0);
|
||||
testCounters(counters, db.get(), true);
|
||||
}
|
||||
|
||||
{
|
||||
std::cout << "Test merge-based counters... \n";
|
||||
MergeBasedCounters counters(db, 0);
|
||||
testCounters(counters, db.get(), compact);
|
||||
testCounters(counters, db.get(), use_compression);
|
||||
}
|
||||
}
|
||||
|
||||
DestroyDB(dbname, Options());
|
||||
|
||||
{
|
||||
std::cout << "Test merge in memtable... \n";
|
||||
size_t max_merge = 5;
|
||||
auto db = OpenDb(dbname, use_ttl, max_merge);
|
||||
MergeBasedCounters counters(db, 0);
|
||||
testCounters(counters, db.get(), compact);
|
||||
testCounters(counters, db.get(), use_compression);
|
||||
testSuccessiveMerge(counters, max_merge, max_merge * 2);
|
||||
testSingleBatchSuccessiveMerge(db.get(), 5, 7);
|
||||
DestroyDB(dbname, Options());
|
||||
}
|
||||
|
||||
{
|
||||
std::cout << "Test Partial-Merge\n";
|
||||
size_t max_merge = 100;
|
||||
// Min merge is hard-coded to 2.
|
||||
uint32_t min_merge = 2;
|
||||
@@ -468,7 +450,6 @@ void runTest(int argc, const std::string& dbname, const bool use_ttl = false) {
|
||||
}
|
||||
|
||||
{
|
||||
std::cout << "Test merge-operator not set after reopen\n";
|
||||
{
|
||||
auto db = OpenDb(dbname);
|
||||
MergeBasedCounters counters(db, 0);
|
||||
@@ -502,16 +483,27 @@ void runTest(int argc, const std::string& dbname, const bool use_ttl = false) {
|
||||
}
|
||||
*/
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* /*argv*/ []) {
|
||||
//TODO: Make this test like a general rocksdb unit-test
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
runTest(argc, test::TmpDir() + "/merge_testdb");
|
||||
// DBWithTTL is not supported in ROCKSDB_LITE
|
||||
#ifndef ROCKSDB_LITE
|
||||
runTest(argc, test::TmpDir() + "/merge_testdbttl", true); // Run test on TTL database
|
||||
#endif // !ROCKSDB_LITE
|
||||
printf("Passed all tests!\n");
|
||||
return 0;
|
||||
TEST_F(MergeTest, MergeDbTest) {
|
||||
runTest(test::PerThreadDBPath("merge_testdb"));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(MergeTest, MergeDbTtlTest) {
|
||||
runTest(test::PerThreadDBPath("merge_testdbttl"),
|
||||
true); // Run test on TTL database
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::use_compression = false;
|
||||
if (argc > 1) {
|
||||
rocksdb::use_compression = true;
|
||||
}
|
||||
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class ObsoleteFilesTest : public testing::Test {
|
||||
options_.max_bytes_for_level_base = 1024*1024*1000;
|
||||
options_.WAL_ttl_seconds = 300; // Used to test log files
|
||||
options_.WAL_size_limit_MB = 1024; // Used to test log files
|
||||
dbname_ = test::TmpDir() + "/obsolete_files_test";
|
||||
dbname_ = test::PerThreadDBPath("obsolete_files_test");
|
||||
options_.wal_dir = dbname_ + "/wal_files";
|
||||
|
||||
// clean up all the files that might have been there before
|
||||
@@ -198,6 +198,47 @@ TEST_F(ObsoleteFilesTest, RaceForObsoleteFileDeletion) {
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
TEST_F(ObsoleteFilesTest, DeleteObsoleteOptionsFile) {
|
||||
createLevel0Files(2, 50000);
|
||||
CheckFileTypeCounts(options_.wal_dir, 1, 0, 0);
|
||||
|
||||
std::vector<uint64_t> optsfiles_nums;
|
||||
std::vector<bool> optsfiles_keep;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:1", [&](void* arg) {
|
||||
optsfiles_nums.push_back(*reinterpret_cast<uint64_t*>(arg));
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::PurgeObsoleteFiles:CheckOptionsFiles:2", [&](void* arg) {
|
||||
optsfiles_keep.push_back(*reinterpret_cast<bool*>(arg));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
DBImpl* dbi = static_cast<DBImpl*>(db_);
|
||||
ASSERT_OK(dbi->DisableFileDeletions());
|
||||
for (int i = 0; i != 4; ++i) {
|
||||
if (i % 2) {
|
||||
ASSERT_OK(dbi->SetOptions(dbi->DefaultColumnFamily(),
|
||||
{{"paranoid_file_checks", "false"}}));
|
||||
} else {
|
||||
ASSERT_OK(dbi->SetOptions(dbi->DefaultColumnFamily(),
|
||||
{{"paranoid_file_checks", "true"}}));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(dbi->EnableFileDeletions(true /* force */));
|
||||
ASSERT_EQ(optsfiles_nums.size(), optsfiles_keep.size());
|
||||
int size = static_cast<int>(optsfiles_nums.size());
|
||||
int kept_opts_files_count = 0;
|
||||
for (int i = 0; i != size; ++i) {
|
||||
if (optsfiles_keep[i]) {
|
||||
++kept_opts_files_count;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(2, kept_opts_files_count);
|
||||
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
} //namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace rocksdb {
|
||||
class OptionsFileTest : public testing::Test {
|
||||
public:
|
||||
OptionsFileTest() : dbname_(test::TmpDir() + "/options_file_test") {}
|
||||
OptionsFileTest() : dbname_(test::PerThreadDBPath("options_file_test")) {}
|
||||
|
||||
std::string dbname_;
|
||||
};
|
||||
|
||||
@@ -30,7 +30,7 @@ int FLAGS_min_write_buffer_number_to_merge = 7;
|
||||
bool FLAGS_verbose = false;
|
||||
|
||||
// Path to the database on file system
|
||||
const std::string kDbName = rocksdb::test::TmpDir() + "/perf_context_test";
|
||||
const std::string kDbName = rocksdb::test::PerThreadDBPath("perf_context_test");
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ class PlainTableDBTest : public testing::Test,
|
||||
|
||||
void SetUp() override {
|
||||
mmap_mode_ = GetParam();
|
||||
dbname_ = test::TmpDir() + "/plain_table_db_test";
|
||||
dbname_ = test::PerThreadDBPath("plain_table_db_test");
|
||||
EXPECT_OK(DestroyDB(dbname_, Options()));
|
||||
db_ = nullptr;
|
||||
Reopen();
|
||||
@@ -333,21 +333,23 @@ class TestPlainTableFactory : public PlainTableFactory {
|
||||
TableProperties* props = nullptr;
|
||||
auto s =
|
||||
ReadTableProperties(file.get(), file_size, kPlainTableMagicNumber,
|
||||
table_reader_options.ioptions, &props);
|
||||
table_reader_options.ioptions, &props,
|
||||
true /* compression_type_missing */);
|
||||
EXPECT_TRUE(s.ok());
|
||||
|
||||
if (store_index_in_file_) {
|
||||
BlockHandle bloom_block_handle;
|
||||
s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
|
||||
table_reader_options.ioptions,
|
||||
BloomBlockBuilder::kBloomBlock, &bloom_block_handle);
|
||||
BloomBlockBuilder::kBloomBlock, &bloom_block_handle,
|
||||
/* compression_type_missing */ true);
|
||||
EXPECT_TRUE(s.ok());
|
||||
|
||||
BlockHandle index_block_handle;
|
||||
s = FindMetaBlock(file.get(), file_size, kPlainTableMagicNumber,
|
||||
table_reader_options.ioptions,
|
||||
PlainTableIndexBuilder::kPlainTableIndexBlock,
|
||||
&index_block_handle);
|
||||
&index_block_handle, /* compression_type_missing */ true);
|
||||
EXPECT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -53,7 +53,7 @@ DEFINE_int32(value_size, 40, "");
|
||||
DEFINE_bool(enable_print, false, "Print options generated to console.");
|
||||
|
||||
// Path to the database on file system
|
||||
const std::string kDbName = rocksdb::test::TmpDir() + "/prefix_test";
|
||||
const std::string kDbName = rocksdb::test::PerThreadDBPath("prefix_test");
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -212,6 +212,10 @@ class SamePrefixTransform : public SliceTransform {
|
||||
virtual bool InRange(const Slice& dst) const override {
|
||||
return dst == prefix_;
|
||||
}
|
||||
|
||||
virtual bool FullLengthEnabled(size_t* /*len*/) const override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
+491
-424
File diff suppressed because it is too large
Load Diff
+79
-72
@@ -5,6 +5,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
@@ -23,6 +24,54 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// RangeDelMaps maintain position across calls to ShouldDelete. The caller may
|
||||
// wish to specify a mode to optimize positioning the iterator during the next
|
||||
// call to ShouldDelete. The non-kFullScan modes are only available when
|
||||
// deletion collapsing is enabled.
|
||||
//
|
||||
// For example, if we invoke Next() on an iterator, kForwardTraversal should be
|
||||
// specified to advance one-by-one through deletions until one is found with its
|
||||
// interval containing the key. This will typically be faster than doing a full
|
||||
// binary search (kBinarySearch).
|
||||
enum class RangeDelPositioningMode {
|
||||
kFullScan, // used iff collapse_deletions_ == false
|
||||
kForwardTraversal,
|
||||
kBackwardTraversal,
|
||||
kBinarySearch,
|
||||
};
|
||||
|
||||
// A RangeDelIterator iterates over range deletion tombstones.
|
||||
class RangeDelIterator {
|
||||
public:
|
||||
virtual ~RangeDelIterator() = default;
|
||||
|
||||
virtual bool Valid() const = 0;
|
||||
virtual void Next() = 0;
|
||||
virtual void Seek(const Slice& target) = 0;
|
||||
virtual RangeTombstone Tombstone() const = 0;
|
||||
};
|
||||
|
||||
// A RangeDelMap keeps track of range deletion tombstones within a snapshot
|
||||
// stripe.
|
||||
//
|
||||
// RangeDelMaps are used internally by RangeDelAggregator. They are not intended
|
||||
// to be used directly.
|
||||
class RangeDelMap {
|
||||
public:
|
||||
virtual ~RangeDelMap() = default;
|
||||
|
||||
virtual bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode) = 0;
|
||||
virtual bool IsRangeOverlapped(const Slice& start, const Slice& end) = 0;
|
||||
virtual void InvalidatePosition() = 0;
|
||||
|
||||
virtual size_t Size() const = 0;
|
||||
bool IsEmpty() const { return Size() == 0; }
|
||||
|
||||
virtual void AddTombstone(RangeTombstone tombstone) = 0;
|
||||
virtual std::unique_ptr<RangeDelIterator> NewIterator() = 0;
|
||||
};
|
||||
|
||||
// A RangeDelAggregator aggregates range deletion tombstones as they are
|
||||
// encountered in memtables/SST files. It provides methods that check whether a
|
||||
// key is covered by range tombstones or write the relevant tombstones to a new
|
||||
@@ -53,45 +102,31 @@ class RangeDelAggregator {
|
||||
SequenceNumber upper_bound,
|
||||
bool collapse_deletions = false);
|
||||
|
||||
// We maintain position in the tombstone map across calls to ShouldDelete. The
|
||||
// caller may wish to specify a mode to optimize positioning the iterator
|
||||
// during the next call to ShouldDelete. The non-kFullScan modes are only
|
||||
// available when deletion collapsing is enabled.
|
||||
//
|
||||
// For example, if we invoke Next() on an iterator, kForwardTraversal should
|
||||
// be specified to advance one-by-one through deletions until one is found
|
||||
// with its interval containing the key. This will typically be faster than
|
||||
// doing a full binary search (kBinarySearch).
|
||||
enum RangePositioningMode {
|
||||
kFullScan, // used iff collapse_deletions_ == false
|
||||
kForwardTraversal,
|
||||
kBackwardTraversal,
|
||||
kBinarySearch,
|
||||
};
|
||||
|
||||
// Returns whether the key should be deleted, which is the case when it is
|
||||
// covered by a range tombstone residing in the same snapshot stripe.
|
||||
// @param mode If collapse_deletions_ is true, this dictates how we will find
|
||||
// the deletion whose interval contains this key. Otherwise, its
|
||||
// value must be kFullScan indicating linear scan from beginning..
|
||||
bool ShouldDelete(const ParsedInternalKey& parsed,
|
||||
RangePositioningMode mode = kFullScan) {
|
||||
// value must be kFullScan indicating linear scan from beginning.
|
||||
bool ShouldDelete(
|
||||
const ParsedInternalKey& parsed,
|
||||
RangeDelPositioningMode mode = RangeDelPositioningMode::kFullScan) {
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return ShouldDeleteImpl(parsed, mode);
|
||||
}
|
||||
bool ShouldDelete(const Slice& internal_key,
|
||||
RangePositioningMode mode = kFullScan) {
|
||||
bool ShouldDelete(
|
||||
const Slice& internal_key,
|
||||
RangeDelPositioningMode mode = RangeDelPositioningMode::kFullScan) {
|
||||
if (rep_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return ShouldDeleteImpl(internal_key, mode);
|
||||
}
|
||||
bool ShouldDeleteImpl(const ParsedInternalKey& parsed,
|
||||
RangePositioningMode mode = kFullScan);
|
||||
RangeDelPositioningMode mode);
|
||||
bool ShouldDeleteImpl(const Slice& internal_key,
|
||||
RangePositioningMode mode = kFullScan);
|
||||
RangeDelPositioningMode mode);
|
||||
|
||||
// Checks whether range deletions cover any keys between `start` and `end`,
|
||||
// inclusive.
|
||||
@@ -102,72 +137,44 @@ class RangeDelAggregator {
|
||||
// `end` causes this to return true.
|
||||
bool IsRangeOverlapped(const Slice& start, const Slice& end);
|
||||
|
||||
bool ShouldAddTombstones(bool bottommost_level = false);
|
||||
|
||||
// Adds tombstones to the tombstone aggregation structure maintained by this
|
||||
// object.
|
||||
// object. Tombstones are truncated to smallest and largest. If smallest (or
|
||||
// largest) is null, it is not used for truncation. When adding range
|
||||
// tombstones present in an sstable, smallest and largest should be set to
|
||||
// the smallest and largest keys from the sstable file metadata. Note that
|
||||
// tombstones end keys are exclusive while largest is inclusive.
|
||||
// @return non-OK status if any of the tombstone keys are corrupted.
|
||||
Status AddTombstones(std::unique_ptr<InternalIterator> input);
|
||||
Status AddTombstones(std::unique_ptr<InternalIterator> input,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr);
|
||||
|
||||
// Resets iterators maintained across calls to ShouldDelete(). This may be
|
||||
// called when the tombstones change, or the owner may call explicitly, e.g.,
|
||||
// if it's an iterator that just seeked to an arbitrary position. The effect
|
||||
// of invalidation is that the following call to ShouldDelete() will binary
|
||||
// search for its tombstone.
|
||||
void InvalidateTombstoneMapPositions();
|
||||
void InvalidateRangeDelMapPositions();
|
||||
|
||||
// Writes tombstones covering a range to a table builder.
|
||||
// @param extend_before_min_key If true, the range of tombstones to be added
|
||||
// to the TableBuilder starts from the beginning of the key-range;
|
||||
// otherwise, it starts from meta->smallest.
|
||||
// @param lower_bound/upper_bound Any range deletion with [start_key, end_key)
|
||||
// that overlaps the target range [*lower_bound, *upper_bound) is added to
|
||||
// the builder. If lower_bound is nullptr, the target range extends
|
||||
// infinitely to the left. If upper_bound is nullptr, the target range
|
||||
// extends infinitely to the right. If both are nullptr, the target range
|
||||
// extends infinitely in both directions, i.e., all range deletions are
|
||||
// added to the builder.
|
||||
// @param meta The file's metadata. We modify the begin and end keys according
|
||||
// to the range tombstones added to this file such that the read path does
|
||||
// not miss range tombstones that cover gaps before/after/between files in
|
||||
// a level. lower_bound/upper_bound above constrain how far file boundaries
|
||||
// can be extended.
|
||||
// @param bottommost_level If true, we will filter out any tombstones
|
||||
// belonging to the oldest snapshot stripe, because all keys potentially
|
||||
// covered by this tombstone are guaranteed to have been deleted by
|
||||
// compaction.
|
||||
void AddToBuilder(TableBuilder* builder, const Slice* lower_bound,
|
||||
const Slice* upper_bound, FileMetaData* meta,
|
||||
CompactionIterationStats* range_del_out_stats = nullptr,
|
||||
bool bottommost_level = false);
|
||||
bool IsEmpty();
|
||||
bool AddFile(uint64_t file_number);
|
||||
|
||||
// Create a new iterator over the range deletion tombstones in all of the
|
||||
// snapshot stripes in this aggregator. Tombstones are presented in start key
|
||||
// order. Tombstones with the same start key are presented in arbitrary order.
|
||||
//
|
||||
// The iterator is invalidated after any call to AddTombstones. It is the
|
||||
// caller's responsibility to avoid using invalid iterators.
|
||||
std::unique_ptr<RangeDelIterator> NewIterator();
|
||||
|
||||
private:
|
||||
// Maps tombstone user start key -> tombstone object
|
||||
typedef std::multimap<Slice, RangeTombstone, stl_wrappers::LessOfComparator>
|
||||
TombstoneMap;
|
||||
// Also maintains position in TombstoneMap last seen by ShouldDelete(). The
|
||||
// end iterator indicates invalidation (e.g., if AddTombstones() changes the
|
||||
// underlying map). End iterator cannot be invalidated.
|
||||
struct PositionalTombstoneMap {
|
||||
explicit PositionalTombstoneMap(TombstoneMap _raw_map)
|
||||
: raw_map(std::move(_raw_map)), iter(raw_map.end()) {}
|
||||
PositionalTombstoneMap(const PositionalTombstoneMap&) = delete;
|
||||
PositionalTombstoneMap(PositionalTombstoneMap&& other)
|
||||
: raw_map(std::move(other.raw_map)), iter(raw_map.end()) {}
|
||||
|
||||
TombstoneMap raw_map;
|
||||
TombstoneMap::const_iterator iter;
|
||||
};
|
||||
|
||||
// Maps snapshot seqnum -> map of tombstones that fall in that stripe, i.e.,
|
||||
// their seqnums are greater than the next smaller snapshot's seqnum.
|
||||
typedef std::map<SequenceNumber, PositionalTombstoneMap> StripeMap;
|
||||
typedef std::map<SequenceNumber, std::unique_ptr<RangeDelMap>> StripeMap;
|
||||
|
||||
struct Rep {
|
||||
StripeMap stripe_map_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
std::list<std::string> pinned_slices_;
|
||||
std::set<uint64_t> added_files_;
|
||||
};
|
||||
// Initializes rep_ lazily. This aggregator object is constructed for every
|
||||
@@ -175,8 +182,8 @@ class RangeDelAggregator {
|
||||
// once the first range deletion is encountered.
|
||||
void InitRep(const std::vector<SequenceNumber>& snapshots);
|
||||
|
||||
PositionalTombstoneMap& GetPositionalTombstoneMap(SequenceNumber seq);
|
||||
Status AddTombstone(RangeTombstone tombstone);
|
||||
std::unique_ptr<RangeDelMap> NewRangeDelMap();
|
||||
RangeDelMap& GetRangeDelMap(SequenceNumber seq);
|
||||
|
||||
SequenceNumber upper_bound_;
|
||||
std::unique_ptr<Rep> rep_;
|
||||
|
||||
+192
-67
@@ -19,6 +19,7 @@ namespace {
|
||||
struct ExpectedPoint {
|
||||
Slice begin;
|
||||
SequenceNumber seq;
|
||||
bool expectAlive;
|
||||
};
|
||||
|
||||
enum Direction {
|
||||
@@ -26,45 +27,12 @@ enum Direction {
|
||||
kReverse,
|
||||
};
|
||||
|
||||
void VerifyRangeDels(const std::vector<RangeTombstone>& range_dels,
|
||||
const std::vector<ExpectedPoint>& expected_points) {
|
||||
auto icmp = InternalKeyComparator(BytewiseComparator());
|
||||
// Test same result regardless of which order the range deletions are added.
|
||||
for (Direction dir : {kForward, kReverse}) {
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */, true);
|
||||
std::vector<std::string> keys, values;
|
||||
for (const auto& range_del : range_dels) {
|
||||
auto key_and_value = range_del.Serialize();
|
||||
keys.push_back(key_and_value.first.Encode().ToString());
|
||||
values.push_back(key_and_value.second.ToString());
|
||||
}
|
||||
if (dir == kReverse) {
|
||||
std::reverse(keys.begin(), keys.end());
|
||||
std::reverse(values.begin(), values.end());
|
||||
}
|
||||
std::unique_ptr<test::VectorIterator> range_del_iter(
|
||||
new test::VectorIterator(keys, values));
|
||||
range_del_agg.AddTombstones(std::move(range_del_iter));
|
||||
static auto icmp = InternalKeyComparator(BytewiseComparator());
|
||||
|
||||
for (const auto expected_point : expected_points) {
|
||||
ParsedInternalKey parsed_key;
|
||||
parsed_key.user_key = expected_point.begin;
|
||||
parsed_key.sequence = expected_point.seq;
|
||||
parsed_key.type = kTypeValue;
|
||||
ASSERT_FALSE(range_del_agg.ShouldDelete(
|
||||
parsed_key,
|
||||
RangeDelAggregator::RangePositioningMode::kForwardTraversal));
|
||||
if (parsed_key.sequence > 0) {
|
||||
--parsed_key.sequence;
|
||||
ASSERT_TRUE(range_del_agg.ShouldDelete(
|
||||
parsed_key,
|
||||
RangeDelAggregator::RangePositioningMode::kForwardTraversal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */,
|
||||
false /* collapse_deletions */);
|
||||
void AddTombstones(RangeDelAggregator* range_del_agg,
|
||||
const std::vector<RangeTombstone>& range_dels,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr) {
|
||||
std::vector<std::string> keys, values;
|
||||
for (const auto& range_del : range_dels) {
|
||||
auto key_and_value = range_del.Serialize();
|
||||
@@ -73,7 +41,87 @@ void VerifyRangeDels(const std::vector<RangeTombstone>& range_dels,
|
||||
}
|
||||
std::unique_ptr<test::VectorIterator> range_del_iter(
|
||||
new test::VectorIterator(keys, values));
|
||||
range_del_agg.AddTombstones(std::move(range_del_iter));
|
||||
range_del_agg->AddTombstones(std::move(range_del_iter), smallest, largest);
|
||||
}
|
||||
|
||||
void VerifyTombstonesEq(const RangeTombstone& a, const RangeTombstone& b) {
|
||||
ASSERT_EQ(a.seq_, b.seq_);
|
||||
ASSERT_EQ(a.start_key_, b.start_key_);
|
||||
ASSERT_EQ(a.end_key_, b.end_key_);
|
||||
}
|
||||
|
||||
void VerifyRangeDelIter(
|
||||
RangeDelIterator* range_del_iter,
|
||||
const std::vector<RangeTombstone>& expected_range_dels) {
|
||||
size_t i = 0;
|
||||
for (; range_del_iter->Valid() && i < expected_range_dels.size();
|
||||
range_del_iter->Next(), i++) {
|
||||
VerifyTombstonesEq(expected_range_dels[i], range_del_iter->Tombstone());
|
||||
}
|
||||
ASSERT_EQ(expected_range_dels.size(), i);
|
||||
ASSERT_FALSE(range_del_iter->Valid());
|
||||
}
|
||||
|
||||
void VerifyRangeDels(
|
||||
const std::vector<RangeTombstone>& range_dels_in,
|
||||
const std::vector<ExpectedPoint>& expected_points,
|
||||
const std::vector<RangeTombstone>& expected_collapsed_range_dels,
|
||||
const InternalKey* smallest = nullptr,
|
||||
const InternalKey* largest = nullptr) {
|
||||
// Test same result regardless of which order the range deletions are added
|
||||
// and regardless of collapsed mode.
|
||||
for (bool collapsed : {false, true}) {
|
||||
for (Direction dir : {kForward, kReverse}) {
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */, collapsed);
|
||||
|
||||
std::vector<RangeTombstone> range_dels = range_dels_in;
|
||||
if (dir == kReverse) {
|
||||
std::reverse(range_dels.begin(), range_dels.end());
|
||||
}
|
||||
AddTombstones(&range_del_agg, range_dels, smallest, largest);
|
||||
|
||||
auto mode = RangeDelPositioningMode::kFullScan;
|
||||
if (collapsed) {
|
||||
mode = RangeDelPositioningMode::kForwardTraversal;
|
||||
}
|
||||
|
||||
for (const auto expected_point : expected_points) {
|
||||
ParsedInternalKey parsed_key;
|
||||
parsed_key.user_key = expected_point.begin;
|
||||
parsed_key.sequence = expected_point.seq;
|
||||
parsed_key.type = kTypeValue;
|
||||
ASSERT_FALSE(range_del_agg.ShouldDelete(parsed_key, mode));
|
||||
if (parsed_key.sequence > 0) {
|
||||
--parsed_key.sequence;
|
||||
if (expected_point.expectAlive) {
|
||||
ASSERT_FALSE(range_del_agg.ShouldDelete(parsed_key, mode));
|
||||
} else {
|
||||
ASSERT_TRUE(range_del_agg.ShouldDelete(parsed_key, mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collapsed) {
|
||||
range_dels = expected_collapsed_range_dels;
|
||||
VerifyRangeDelIter(range_del_agg.NewIterator().get(), range_dels);
|
||||
} else if (smallest == nullptr && largest == nullptr) {
|
||||
// Tombstones in an uncollapsed map are presented in start key
|
||||
// order. Tombstones with the same start key are presented in
|
||||
// insertion order. We don't handle tombstone truncation here, so the
|
||||
// verification is only performed if no truncation was requested.
|
||||
std::stable_sort(range_dels.begin(), range_dels.end(),
|
||||
[&](const RangeTombstone& a, const RangeTombstone& b) {
|
||||
return icmp.user_comparator()->Compare(
|
||||
a.start_key_, b.start_key_) < 0;
|
||||
});
|
||||
VerifyRangeDelIter(range_del_agg.NewIterator().get(), range_dels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */,
|
||||
false /* collapse_deletions */);
|
||||
AddTombstones(&range_del_agg, range_dels_in);
|
||||
for (size_t i = 1; i < expected_points.size(); ++i) {
|
||||
bool overlapped = range_del_agg.IsRangeOverlapped(
|
||||
expected_points[i - 1].begin, expected_points[i].begin);
|
||||
@@ -87,60 +135,69 @@ void VerifyRangeDels(const std::vector<RangeTombstone>& range_dels,
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, Empty) { VerifyRangeDels({}, {{"a", 0}}); }
|
||||
TEST_F(RangeDelAggregatorTest, Empty) { VerifyRangeDels({}, {{"a", 0}}, {}); }
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, SameStartAndEnd) {
|
||||
VerifyRangeDels({{"a", "a", 5}}, {{" ", 0}, {"a", 0}, {"b", 0}});
|
||||
VerifyRangeDels({{"a", "a", 5}}, {{" ", 0}, {"a", 0}, {"b", 0}}, {});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, Single) {
|
||||
VerifyRangeDels({{"a", "b", 10}}, {{" ", 0}, {"a", 10}, {"b", 0}});
|
||||
VerifyRangeDels({{"a", "b", 10}}, {{" ", 0}, {"a", 10}, {"b", 0}},
|
||||
{{"a", "b", 10}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapAboveLeft) {
|
||||
VerifyRangeDels({{"a", "c", 10}, {"b", "d", 5}},
|
||||
{{" ", 0}, {"a", 10}, {"c", 5}, {"d", 0}});
|
||||
{{" ", 0}, {"a", 10}, {"c", 5}, {"d", 0}},
|
||||
{{"a", "c", 10}, {"c", "d", 5}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapAboveRight) {
|
||||
VerifyRangeDels({{"a", "c", 5}, {"b", "d", 10}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"d", 0}});
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"d", 0}},
|
||||
{{"a", "b", 5}, {"b", "d", 10}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapAboveMiddle) {
|
||||
VerifyRangeDels({{"a", "d", 5}, {"b", "c", 10}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"c", 5}, {"d", 0}});
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"c", 5}, {"d", 0}},
|
||||
{{"a", "b", 5}, {"b", "c", 10}, {"c", "d", 5}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapFully) {
|
||||
VerifyRangeDels({{"a", "d", 10}, {"b", "c", 5}},
|
||||
{{" ", 0}, {"a", 10}, {"d", 0}});
|
||||
{{" ", 0}, {"a", 10}, {"d", 0}}, {{"a", "d", 10}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, OverlapPoint) {
|
||||
VerifyRangeDels({{"a", "b", 5}, {"b", "c", 10}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"c", 0}});
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"c", 0}},
|
||||
{{"a", "b", 5}, {"b", "c", 10}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, SameStartKey) {
|
||||
VerifyRangeDels({{"a", "c", 5}, {"a", "b", 10}},
|
||||
{{" ", 0}, {"a", 10}, {"b", 5}, {"c", 0}});
|
||||
{{" ", 0}, {"a", 10}, {"b", 5}, {"c", 0}},
|
||||
{{"a", "b", 10}, {"b", "c", 5}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, SameEndKey) {
|
||||
VerifyRangeDels({{"a", "d", 5}, {"b", "d", 10}},
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"d", 0}});
|
||||
{{" ", 0}, {"a", 5}, {"b", 10}, {"d", 0}},
|
||||
{{"a", "b", 5}, {"b", "d", 10}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, GapsBetweenRanges) {
|
||||
VerifyRangeDels({{"a", "b", 5}, {"c", "d", 10}, {"e", "f", 15}}, {{" ", 0},
|
||||
{"a", 5},
|
||||
{"b", 0},
|
||||
{"c", 10},
|
||||
{"d", 0},
|
||||
{"da", 0},
|
||||
{"e", 15},
|
||||
{"f", 0}});
|
||||
VerifyRangeDels({{"a", "b", 5}, {"c", "d", 10}, {"e", "f", 15}},
|
||||
{{" ", 0},
|
||||
{"a", 5},
|
||||
{"b", 0},
|
||||
{"c", 10},
|
||||
{"d", 0},
|
||||
{"da", 0},
|
||||
{"e", 15},
|
||||
{"f", 0}},
|
||||
{{"a", "b", 5}, {"c", "d", 10}, {"e", "f", 15}});
|
||||
}
|
||||
|
||||
// Note the Cover* tests also test cases where tombstones are inserted under a
|
||||
@@ -148,31 +205,99 @@ TEST_F(RangeDelAggregatorTest, GapsBetweenRanges) {
|
||||
TEST_F(RangeDelAggregatorTest, CoverMultipleFromLeft) {
|
||||
VerifyRangeDels(
|
||||
{{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"a", "f", 20}},
|
||||
{{" ", 0}, {"a", 20}, {"f", 15}, {"g", 0}});
|
||||
{{" ", 0}, {"a", 20}, {"f", 15}, {"g", 0}},
|
||||
{{"a", "f", 20}, {"f", "g", 15}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, CoverMultipleFromRight) {
|
||||
VerifyRangeDels(
|
||||
{{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"c", "h", 20}},
|
||||
{{" ", 0}, {"b", 5}, {"c", 20}, {"h", 0}});
|
||||
{{" ", 0}, {"b", 5}, {"c", 20}, {"h", 0}},
|
||||
{{"b", "c", 5}, {"c", "h", 20}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, CoverMultipleFully) {
|
||||
VerifyRangeDels(
|
||||
{{"b", "d", 5}, {"c", "f", 10}, {"e", "g", 15}, {"a", "h", 20}},
|
||||
{{" ", 0}, {"a", 20}, {"h", 0}});
|
||||
{{" ", 0}, {"a", 20}, {"h", 0}}, {{"a", "h", 20}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, AlternateMultipleAboveBelow) {
|
||||
VerifyRangeDels(
|
||||
{{"b", "d", 15}, {"c", "f", 10}, {"e", "g", 20}, {"a", "h", 5}},
|
||||
{{" ", 0},
|
||||
{"a", 5},
|
||||
{"b", 15},
|
||||
{"d", 10},
|
||||
{"e", 20},
|
||||
{"g", 5},
|
||||
{"h", 0}});
|
||||
{{" ", 0}, {"a", 5}, {"b", 15}, {"d", 10}, {"e", 20}, {"g", 5}, {"h", 0}},
|
||||
{{"a", "b", 5},
|
||||
{"b", "d", 15},
|
||||
{"d", "e", 10},
|
||||
{"e", "g", 20},
|
||||
{"g", "h", 5}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, MergingIteratorAllEmptyStripes) {
|
||||
for (bool collapsed : {true, false}) {
|
||||
RangeDelAggregator range_del_agg(icmp, {1, 2}, collapsed);
|
||||
VerifyRangeDelIter(range_del_agg.NewIterator().get(), {});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, MergingIteratorOverlappingStripes) {
|
||||
for (bool collapsed : {true, false}) {
|
||||
RangeDelAggregator range_del_agg(icmp, {5, 15, 25, 35}, collapsed);
|
||||
AddTombstones(
|
||||
&range_del_agg,
|
||||
{{"d", "e", 10}, {"aa", "b", 20}, {"c", "d", 30}, {"a", "b", 10}});
|
||||
VerifyRangeDelIter(
|
||||
range_del_agg.NewIterator().get(),
|
||||
{{"a", "b", 10}, {"aa", "b", 20}, {"c", "d", 30}, {"d", "e", 10}});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, MergingIteratorSeek) {
|
||||
RangeDelAggregator range_del_agg(icmp, {5, 15}, true /* collapsed */);
|
||||
AddTombstones(&range_del_agg, {{"a", "c", 10},
|
||||
{"b", "c", 11},
|
||||
{"f", "g", 10},
|
||||
{"c", "d", 20},
|
||||
{"e", "f", 20}});
|
||||
auto it = range_del_agg.NewIterator();
|
||||
|
||||
// Verify seek positioning.
|
||||
it->Seek("");
|
||||
VerifyTombstonesEq(it->Tombstone(), {"a", "b", 10});
|
||||
it->Seek("a");
|
||||
VerifyTombstonesEq(it->Tombstone(), {"a", "b", 10});
|
||||
it->Seek("aa");
|
||||
VerifyTombstonesEq(it->Tombstone(), {"a", "b", 10});
|
||||
it->Seek("b");
|
||||
VerifyTombstonesEq(it->Tombstone(), {"b", "c", 11});
|
||||
it->Seek("c");
|
||||
VerifyTombstonesEq(it->Tombstone(), {"c", "d", 20});
|
||||
it->Seek("dd");
|
||||
VerifyTombstonesEq(it->Tombstone(), {"e", "f", 20});
|
||||
it->Seek("f");
|
||||
VerifyTombstonesEq(it->Tombstone(), {"f", "g", 10});
|
||||
it->Seek("g");
|
||||
ASSERT_EQ(it->Valid(), false);
|
||||
it->Seek("h");
|
||||
ASSERT_EQ(it->Valid(), false);
|
||||
|
||||
// Verify iteration after seek.
|
||||
it->Seek("c");
|
||||
VerifyRangeDelIter(it.get(),
|
||||
{{"c", "d", 20}, {"e", "f", 20}, {"f", "g", 10}});
|
||||
}
|
||||
|
||||
TEST_F(RangeDelAggregatorTest, TruncateTombstones) {
|
||||
const InternalKey smallest("b", 1, kTypeRangeDeletion);
|
||||
const InternalKey largest("e", kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
VerifyRangeDels(
|
||||
{{"a", "c", 10}, {"d", "f", 10}},
|
||||
{{"a", 10, true}, // truncated
|
||||
{"b", 10, false}, // not truncated
|
||||
{"d", 10, false}, // not truncated
|
||||
{"e", 10, true}}, // truncated
|
||||
{{"b", "c", 10}, {"d", "e", 10}},
|
||||
&smallest, &largest);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+12
-3
@@ -13,9 +13,18 @@ class ReadCallback {
|
||||
public:
|
||||
virtual ~ReadCallback() {}
|
||||
|
||||
// Will be called to see if the seq number accepted; if not it moves on to the
|
||||
// next seq number.
|
||||
virtual bool IsCommitted(SequenceNumber seq) = 0;
|
||||
// Will be called to see if the seq number visible; if not it moves on to
|
||||
// the next seq number.
|
||||
virtual bool IsVisible(SequenceNumber seq) = 0;
|
||||
|
||||
// This is called to determine the maximum visible sequence number for the
|
||||
// current transaction for read-your-own-write semantics. This is so that
|
||||
// for write unprepared, we will not skip keys that are written by the
|
||||
// current transaction with the seek to snapshot optimization.
|
||||
//
|
||||
// For other uses, this returns zero, meaning that the current snapshot
|
||||
// sequence number is the maximum visible sequence number.
|
||||
inline virtual SequenceNumber MaxUnpreparedSequenceNumber() { return 0; };
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+2
-2
@@ -341,7 +341,7 @@ class Repairer {
|
||||
return status;
|
||||
}
|
||||
unique_ptr<SequentialFileReader> lfile_reader(
|
||||
new SequentialFileReader(std::move(lfile)));
|
||||
new SequentialFileReader(std::move(lfile), logname));
|
||||
|
||||
// Create the log reader.
|
||||
LogReporter reporter;
|
||||
@@ -500,7 +500,7 @@ class Repairer {
|
||||
}
|
||||
if (status.ok()) {
|
||||
InternalIterator* iter = table_cache_->NewIterator(
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), t->meta.fd,
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), t->meta,
|
||||
nullptr /* range_del_agg */,
|
||||
cfd->GetLatestMutableCFOptions()->prefix_extractor.get());
|
||||
bool empty = true;
|
||||
|
||||
+18
-45
@@ -65,7 +65,10 @@ void AppendVarint64(IterKey* key, uint64_t v) {
|
||||
|
||||
TableCache::TableCache(const ImmutableCFOptions& ioptions,
|
||||
const EnvOptions& env_options, Cache* const cache)
|
||||
: ioptions_(ioptions), env_options_(env_options), cache_(cache) {
|
||||
: ioptions_(ioptions),
|
||||
env_options_(env_options),
|
||||
cache_(cache),
|
||||
immortal_tables_(false) {
|
||||
if (ioptions_.row_cache) {
|
||||
// If the same cache is shared by multiple instances, we need to
|
||||
// disambiguate its entries.
|
||||
@@ -116,7 +119,8 @@ Status TableCache::GetTableReader(
|
||||
file_read_hist, ioptions_.rate_limiter, for_compaction));
|
||||
s = ioptions_.table_factory->NewTableReader(
|
||||
TableReaderOptions(ioptions_, prefix_extractor, env_options,
|
||||
internal_comparator, skip_filters, level),
|
||||
internal_comparator, skip_filters, immortal_tables_,
|
||||
level),
|
||||
std::move(file_reader), fd.GetFileSize(), table_reader,
|
||||
prefetch_index_and_filter_in_cache);
|
||||
TEST_SYNC_POINT("TableCache::GetTableReader:0");
|
||||
@@ -176,7 +180,7 @@ Status TableCache::FindTable(const EnvOptions& env_options,
|
||||
|
||||
InternalIterator* TableCache::NewIterator(
|
||||
const ReadOptions& options, const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator, const FileDescriptor& fd,
|
||||
const InternalKeyComparator& icomparator, const FileMetaData& file_meta,
|
||||
RangeDelAggregator* range_del_agg, const SliceTransform* prefix_extractor,
|
||||
TableReader** table_reader_ptr, HistogramImpl* file_read_hist,
|
||||
bool for_compaction, Arena* arena, bool skip_filters, int level) {
|
||||
@@ -207,6 +211,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
create_new_table_reader = readahead > 0;
|
||||
}
|
||||
|
||||
auto& fd = file_meta.fd;
|
||||
if (create_new_table_reader) {
|
||||
unique_ptr<TableReader> table_reader_unique_ptr;
|
||||
s = GetTableReader(
|
||||
@@ -236,7 +241,7 @@ InternalIterator* TableCache::NewIterator(
|
||||
result = NewEmptyInternalIterator(arena);
|
||||
} else {
|
||||
result = table_reader->NewIterator(options, prefix_extractor, arena,
|
||||
skip_filters);
|
||||
skip_filters, for_compaction);
|
||||
}
|
||||
if (create_new_table_reader) {
|
||||
assert(handle == nullptr);
|
||||
@@ -261,7 +266,10 @@ InternalIterator* TableCache::NewIterator(
|
||||
s = range_del_iter->status();
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
s = range_del_agg->AddTombstones(
|
||||
std::move(range_del_iter),
|
||||
&file_meta.smallest,
|
||||
&file_meta.largest);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,51 +284,14 @@ InternalIterator* TableCache::NewIterator(
|
||||
return result;
|
||||
}
|
||||
|
||||
InternalIterator* TableCache::NewRangeTombstoneIterator(
|
||||
const ReadOptions& options, const EnvOptions& env_options,
|
||||
const InternalKeyComparator& icomparator, const FileDescriptor& fd,
|
||||
HistogramImpl* file_read_hist, bool skip_filters, int level,
|
||||
const SliceTransform* prefix_extractor) {
|
||||
Status s;
|
||||
Cache::Handle* handle = nullptr;
|
||||
TableReader* table_reader = fd.table_reader;
|
||||
if (table_reader == nullptr) {
|
||||
s = FindTable(env_options, icomparator, fd, &handle, prefix_extractor,
|
||||
options.read_tier == kBlockCacheTier /* no_io */,
|
||||
true /* record read_stats */, file_read_hist, skip_filters,
|
||||
level);
|
||||
if (s.ok()) {
|
||||
table_reader = GetTableReaderFromHandle(handle);
|
||||
}
|
||||
}
|
||||
InternalIterator* result = nullptr;
|
||||
if (s.ok()) {
|
||||
result = table_reader->NewRangeTombstoneIterator(options);
|
||||
if (result != nullptr) {
|
||||
if (handle != nullptr) {
|
||||
result->RegisterCleanup(&UnrefEntry, cache_, handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == nullptr && handle != nullptr) {
|
||||
// the range deletion block didn't exist, or there was a failure between
|
||||
// getting handle and getting iterator.
|
||||
ReleaseHandle(handle);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
assert(result == nullptr);
|
||||
result = NewErrorInternalIterator(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Status TableCache::Get(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& fd, const Slice& k,
|
||||
const FileMetaData& file_meta, const Slice& k,
|
||||
GetContext* get_context,
|
||||
const SliceTransform* prefix_extractor,
|
||||
HistogramImpl* file_read_hist, bool skip_filters,
|
||||
int level) {
|
||||
auto& fd = file_meta.fd;
|
||||
std::string* row_cache_entry = nullptr;
|
||||
bool done = false;
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -401,7 +372,9 @@ Status TableCache::Get(const ReadOptions& options,
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = get_context->range_del_agg()->AddTombstones(
|
||||
std::move(range_del_iter));
|
||||
std::move(range_del_iter),
|
||||
&file_meta.smallest,
|
||||
&file_meta.largest);
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
|
||||
+11
-9
@@ -53,19 +53,12 @@ class TableCache {
|
||||
InternalIterator* NewIterator(
|
||||
const ReadOptions& options, const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& file_fd, RangeDelAggregator* range_del_agg,
|
||||
const FileMetaData& file_meta, RangeDelAggregator* range_del_agg,
|
||||
const SliceTransform* prefix_extractor = nullptr,
|
||||
TableReader** table_reader_ptr = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool for_compaction = false,
|
||||
Arena* arena = nullptr, bool skip_filters = false, int level = -1);
|
||||
|
||||
InternalIterator* NewRangeTombstoneIterator(
|
||||
const ReadOptions& options, const EnvOptions& toptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& file_fd, HistogramImpl* file_read_hist,
|
||||
bool skip_filters, int level,
|
||||
const SliceTransform* prefix_extractor = nullptr);
|
||||
|
||||
// If a seek to internal key "k" in specified file finds an entry,
|
||||
// call (*handle_result)(arg, found_key, found_value) repeatedly until
|
||||
// it returns false.
|
||||
@@ -76,7 +69,7 @@ class TableCache {
|
||||
// @param level The level this table is at, -1 for "not set / don't know"
|
||||
Status Get(const ReadOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const FileDescriptor& file_fd, const Slice& k,
|
||||
const FileMetaData& file_meta, const Slice& k,
|
||||
GetContext* get_context,
|
||||
const SliceTransform* prefix_extractor = nullptr,
|
||||
HistogramImpl* file_read_hist = nullptr, bool skip_filters = false,
|
||||
@@ -132,6 +125,14 @@ class TableCache {
|
||||
// For example when max_open_files is -1 we set the backing Cache to this.
|
||||
static const int kInfiniteCapacity = 0x400000;
|
||||
|
||||
// The tables opened with this TableCache will be immortal, i.e., their
|
||||
// lifetime is as long as that of the DB.
|
||||
void SetTablesAreImmortal() {
|
||||
if (cache_->GetCapacity() >= kInfiniteCapacity) {
|
||||
immortal_tables_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Build a table reader
|
||||
Status GetTableReader(const EnvOptions& env_options,
|
||||
@@ -149,6 +150,7 @@ class TableCache {
|
||||
const EnvOptions& env_options_;
|
||||
Cache* const cache_;
|
||||
std::string row_cache_id_;
|
||||
bool immortal_tables_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -279,7 +279,8 @@ void TestCustomizedTablePropertiesCollector(
|
||||
new test::StringSource(fwf->contents())));
|
||||
TableProperties* props;
|
||||
Status s = ReadTableProperties(fake_file_reader.get(), fwf->contents().size(),
|
||||
magic_number, ioptions, &props);
|
||||
magic_number, ioptions, &props,
|
||||
true /* compression_type_missing */);
|
||||
std::unique_ptr<TableProperties> props_guard(props);
|
||||
ASSERT_OK(s);
|
||||
|
||||
@@ -421,7 +422,7 @@ void TestInternalKeyPropertiesCollector(
|
||||
TableProperties* props;
|
||||
Status s =
|
||||
ReadTableProperties(reader.get(), fwf->contents().size(), magic_number,
|
||||
ioptions, &props);
|
||||
ioptions, &props, true /* compression_type_missing */);
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::unique_ptr<TableProperties> props_guard(props);
|
||||
|
||||
@@ -46,13 +46,14 @@ Status TransactionLogIteratorImpl::OpenLogFile(
|
||||
const LogFile* logFile, unique_ptr<SequentialFileReader>* file_reader) {
|
||||
Env* env = options_->env;
|
||||
unique_ptr<SequentialFile> file;
|
||||
std::string fname;
|
||||
Status s;
|
||||
EnvOptions optimized_env_options = env->OptimizeForLogRead(soptions_);
|
||||
if (logFile->Type() == kArchivedLogFile) {
|
||||
std::string fname = ArchivedLogFileName(dir_, logFile->LogNumber());
|
||||
fname = ArchivedLogFileName(dir_, logFile->LogNumber());
|
||||
s = env->NewSequentialFile(fname, &file, optimized_env_options);
|
||||
} else {
|
||||
std::string fname = LogFileName(dir_, logFile->LogNumber());
|
||||
fname = LogFileName(dir_, logFile->LogNumber());
|
||||
s = env->NewSequentialFile(fname, &file, optimized_env_options);
|
||||
if (!s.ok()) {
|
||||
// If cannot open file in DB directory.
|
||||
@@ -62,7 +63,7 @@ Status TransactionLogIteratorImpl::OpenLogFile(
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
file_reader->reset(new SequentialFileReader(std::move(file)));
|
||||
file_reader->reset(new SequentialFileReader(std::move(file), fname));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -283,7 +284,7 @@ void TransactionLogIteratorImpl::UpdateCurrentWriteBatch(const Slice& record) {
|
||||
const Slice& /*val*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
Status MarkBeginPrepare() override { return Status::OK(); }
|
||||
Status MarkBeginPrepare(bool) override { return Status::OK(); }
|
||||
Status MarkRollback(const Slice&) override { return Status::OK(); }
|
||||
};
|
||||
|
||||
|
||||
+7
-11
@@ -405,17 +405,13 @@ class VersionBuilder::Rep {
|
||||
}
|
||||
};
|
||||
|
||||
if (max_threads <= 1) {
|
||||
load_handlers_func();
|
||||
} else {
|
||||
std::vector<port::Thread> threads;
|
||||
for (int i = 0; i < max_threads; i++) {
|
||||
threads.emplace_back(load_handlers_func);
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
std::vector<port::Thread> threads;
|
||||
for (int i = 1; i < max_threads; i++) {
|
||||
threads.emplace_back(load_handlers_func);
|
||||
}
|
||||
load_handlers_func();
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,21 @@ struct FileMetaData {
|
||||
smallest_seqno = std::min(smallest_seqno, seqno);
|
||||
largest_seqno = std::max(largest_seqno, seqno);
|
||||
}
|
||||
|
||||
// Unlike UpdateBoundaries, ranges do not need to be presented in any
|
||||
// particular order.
|
||||
void UpdateBoundariesForRange(const InternalKey& start,
|
||||
const InternalKey& end, SequenceNumber seqno,
|
||||
const InternalKeyComparator& icmp) {
|
||||
if (smallest.size() == 0 || icmp.Compare(start, smallest) < 0) {
|
||||
smallest = start;
|
||||
}
|
||||
if (largest.size() == 0 || icmp.Compare(largest, end) < 0) {
|
||||
largest = end;
|
||||
}
|
||||
smallest_seqno = std::min(smallest_seqno, seqno);
|
||||
largest_seqno = std::max(largest_seqno, seqno);
|
||||
}
|
||||
};
|
||||
|
||||
// A compressed copy of file meta data that just contain minimum data needed
|
||||
|
||||
+343
-193
@@ -548,8 +548,9 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
|
||||
return table_cache_->NewIterator(
|
||||
read_options_, env_options_, icomparator_, file_meta.fd, range_del_agg_,
|
||||
prefix_extractor_, nullptr /* don't need reference to table */,
|
||||
read_options_, env_options_, icomparator_, *file_meta.file_metadata,
|
||||
range_del_agg_, prefix_extractor_,
|
||||
nullptr /* don't need reference to table */,
|
||||
file_read_hist_, for_compaction_, nullptr /* arena */, skip_filters_,
|
||||
level_);
|
||||
}
|
||||
@@ -761,7 +762,8 @@ Status Version::GetTableProperties(std::shared_ptr<const TableProperties>* tp,
|
||||
new RandomAccessFileReader(std::move(file), file_name));
|
||||
s = ReadTableProperties(
|
||||
file_reader.get(), file_meta->fd.GetFileSize(),
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */, *ioptions, &raw_table_properties);
|
||||
Footer::kInvalidTableMagicNumber /* table's magic number */, *ioptions,
|
||||
&raw_table_properties, false /* compression_type_missing */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -999,7 +1001,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) {
|
||||
const auto& file = storage_info_.LevelFilesBrief(0).files[i];
|
||||
merge_iter_builder->AddIterator(cfd_->table_cache()->NewIterator(
|
||||
read_options, soptions, cfd_->internal_comparator(), file.fd,
|
||||
read_options, soptions, cfd_->internal_comparator(), *file.file_metadata,
|
||||
range_del_agg, mutable_cf_options_.prefix_extractor.get(), nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0), false, arena,
|
||||
false /* skip_filters */, 0 /* level */));
|
||||
@@ -1052,7 +1054,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
continue;
|
||||
}
|
||||
ScopedArenaIterator iter(cfd_->table_cache()->NewIterator(
|
||||
read_options, env_options, cfd_->internal_comparator(), file->fd,
|
||||
read_options, env_options, cfd_->internal_comparator(), *file->file_metadata,
|
||||
&range_del_agg, mutable_cf_options_.prefix_extractor.get(), nullptr,
|
||||
cfd_->internal_stats()->GetFileReadHist(0), false, &arena,
|
||||
false /* skip_filters */, 0 /* level */));
|
||||
@@ -1197,8 +1199,8 @@ void Version::Get(const ReadOptions& read_options, const LookupKey& k,
|
||||
}
|
||||
|
||||
*status = table_cache_->Get(
|
||||
read_options, *internal_comparator(), f->fd, ikey, &get_context,
|
||||
mutable_cf_options_.prefix_extractor.get(),
|
||||
read_options, *internal_comparator(), *f->file_metadata, ikey,
|
||||
&get_context, mutable_cf_options_.prefix_extractor.get(),
|
||||
cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()),
|
||||
IsFilterSkipped(static_cast<int>(fp.GetHitFileLevel()),
|
||||
fp.IsHitFileLastInLevel()),
|
||||
@@ -2052,8 +2054,8 @@ void VersionStorageInfo::GetOverlappingInputs(
|
||||
*file_index = -1;
|
||||
}
|
||||
const Comparator* user_cmp = user_comparator_;
|
||||
if (begin != nullptr && end != nullptr && level > 0) {
|
||||
GetOverlappingInputsRangeBinarySearch(level, user_begin, user_end, inputs,
|
||||
if (level > 0) {
|
||||
GetOverlappingInputsRangeBinarySearch(level, begin, end, inputs,
|
||||
hint_index, file_index);
|
||||
return;
|
||||
}
|
||||
@@ -2108,24 +2110,73 @@ void VersionStorageInfo::GetCleanInputsWithinInterval(
|
||||
return;
|
||||
}
|
||||
|
||||
Slice user_begin, user_end;
|
||||
const auto& level_files = level_files_brief_[level];
|
||||
if (begin == nullptr) {
|
||||
user_begin = ExtractUserKey(level_files.files[0].smallest_key);
|
||||
} else {
|
||||
user_begin = begin->user_key();
|
||||
begin = &level_files.files[0].file_metadata->smallest;
|
||||
}
|
||||
if (end == nullptr) {
|
||||
user_end = ExtractUserKey(
|
||||
level_files.files[level_files.num_files - 1].largest_key);
|
||||
} else {
|
||||
user_end = end->user_key();
|
||||
end = &level_files.files[level_files.num_files - 1].file_metadata->largest;
|
||||
}
|
||||
GetOverlappingInputsRangeBinarySearch(level, user_begin, user_end, inputs,
|
||||
|
||||
GetOverlappingInputsRangeBinarySearch(level, begin, end, inputs,
|
||||
hint_index, file_index,
|
||||
true /* within_interval */);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
const uint64_t kRangeTombstoneSentinel =
|
||||
PackSequenceAndType(kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
|
||||
// Utility for comparing sstable boundary keys. Returns -1 if either a or b is
|
||||
// null which provides the property that a==null indicates a key that is less
|
||||
// than any key and b==null indicates a key that is greater than any key. Note
|
||||
// that the comparison is performed primarily on the user-key portion of the
|
||||
// key. If the user-keys compare equal, an additional test is made to sort
|
||||
// range tombstone sentinel keys before other keys with the same user-key. The
|
||||
// result is that 2 user-keys will compare equal if they differ purely on
|
||||
// their sequence number and value, but the range tombstone sentinel for that
|
||||
// user-key will compare not equal. This is necessary because the range
|
||||
// tombstone sentinel key is set as the largest key for an sstable even though
|
||||
// that key never appears in the database. We don't want adjacent sstables to
|
||||
// be considered overlapping if they are separated by the range tombstone
|
||||
// sentinel.
|
||||
int sstableKeyCompare(const Comparator* user_cmp,
|
||||
const InternalKey& a, const InternalKey& b) {
|
||||
auto c = user_cmp->Compare(a.user_key(), b.user_key());
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
auto a_footer = ExtractInternalKeyFooter(a.Encode());
|
||||
auto b_footer = ExtractInternalKeyFooter(b.Encode());
|
||||
if (a_footer == kRangeTombstoneSentinel) {
|
||||
if (b_footer != kRangeTombstoneSentinel) {
|
||||
return -1;
|
||||
}
|
||||
} else if (b_footer == kRangeTombstoneSentinel) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sstableKeyCompare(const Comparator* user_cmp,
|
||||
const InternalKey* a, const InternalKey& b) {
|
||||
if (a == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
return sstableKeyCompare(user_cmp, *a, b);
|
||||
}
|
||||
|
||||
int sstableKeyCompare(const Comparator* user_cmp,
|
||||
const InternalKey& a, const InternalKey* b) {
|
||||
if (b == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
return sstableKeyCompare(user_cmp, a, *b);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Store in "*inputs" all files in "level" that overlap [begin,end]
|
||||
// Employ binary search to find at least one file that overlaps the
|
||||
// specified range. From that file, iterate backwards and
|
||||
@@ -2134,7 +2185,7 @@ void VersionStorageInfo::GetCleanInputsWithinInterval(
|
||||
// within range [begin, end]. "clean" means there is a boudnary
|
||||
// between the files in "*inputs" and the surrounding files
|
||||
void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch(
|
||||
int level, const Slice& user_begin, const Slice& user_end,
|
||||
int level, const InternalKey* begin, const InternalKey* end,
|
||||
std::vector<FileMetaData*>* inputs, int hint_index, int* file_index,
|
||||
bool within_interval) const {
|
||||
assert(level > 0);
|
||||
@@ -2142,7 +2193,7 @@ void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch(
|
||||
int mid = 0;
|
||||
int max = static_cast<int>(files_[level].size()) - 1;
|
||||
bool foundOverlap = false;
|
||||
const Comparator* user_cmp = user_comparator_;
|
||||
auto user_cmp = user_comparator_;
|
||||
|
||||
// if the caller already knows the index of a file that has overlap,
|
||||
// then we can skip the binary search.
|
||||
@@ -2154,15 +2205,15 @@ void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch(
|
||||
while (!foundOverlap && min <= max) {
|
||||
mid = (min + max)/2;
|
||||
FdWithKeyRange* f = &(level_files_brief_[level].files[mid]);
|
||||
const Slice file_start = ExtractUserKey(f->smallest_key);
|
||||
const Slice file_limit = ExtractUserKey(f->largest_key);
|
||||
if ((!within_interval && user_cmp->Compare(file_limit, user_begin) < 0) ||
|
||||
(within_interval && user_cmp->Compare(file_start, user_begin) < 0)) {
|
||||
auto& smallest = f->file_metadata->smallest;
|
||||
auto& largest = f->file_metadata->largest;
|
||||
if ((!within_interval && sstableKeyCompare(user_cmp, begin, largest) > 0) ||
|
||||
(within_interval && sstableKeyCompare(user_cmp, begin, smallest) > 0)) {
|
||||
min = mid + 1;
|
||||
} else if ((!within_interval &&
|
||||
user_cmp->Compare(user_end, file_start) < 0) ||
|
||||
sstableKeyCompare(user_cmp, smallest, end) > 0) ||
|
||||
(within_interval &&
|
||||
user_cmp->Compare(user_end, file_limit) < 0)) {
|
||||
sstableKeyCompare(user_cmp, largest, end) > 0)) {
|
||||
max = mid - 1;
|
||||
} else {
|
||||
foundOverlap = true;
|
||||
@@ -2181,10 +2232,10 @@ void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch(
|
||||
|
||||
int start_index, end_index;
|
||||
if (within_interval) {
|
||||
ExtendFileRangeWithinInterval(level, user_begin, user_end, mid, &start_index,
|
||||
&end_index);
|
||||
ExtendFileRangeWithinInterval(level, begin, end, mid,
|
||||
&start_index, &end_index);
|
||||
} else {
|
||||
ExtendFileRangeOverlappingInterval(level, user_begin, user_end, mid,
|
||||
ExtendFileRangeOverlappingInterval(level, begin, end, mid,
|
||||
&start_index, &end_index);
|
||||
assert(end_index >= start_index);
|
||||
}
|
||||
@@ -2201,21 +2252,28 @@ void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch(
|
||||
// and forward to find all overlapping files.
|
||||
// Use FileLevel in searching, make it faster
|
||||
void VersionStorageInfo::ExtendFileRangeOverlappingInterval(
|
||||
int level, const Slice& user_begin, const Slice& user_end,
|
||||
int level, const InternalKey* begin, const InternalKey* end,
|
||||
unsigned int mid_index, int* start_index, int* end_index) const {
|
||||
const Comparator* user_cmp = user_comparator_;
|
||||
auto user_cmp = user_comparator_;
|
||||
const FdWithKeyRange* files = level_files_brief_[level].files;
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
// assert that the file at mid_index overlaps with the range
|
||||
assert(mid_index < level_files_brief_[level].num_files);
|
||||
const FdWithKeyRange* f = &files[mid_index];
|
||||
const Slice fstart = ExtractUserKey(f->smallest_key);
|
||||
const Slice flimit = ExtractUserKey(f->largest_key);
|
||||
if (user_cmp->Compare(fstart, user_begin) >= 0) {
|
||||
assert(user_cmp->Compare(fstart, user_end) <= 0);
|
||||
auto& smallest = f->file_metadata->smallest;
|
||||
auto& largest = f->file_metadata->largest;
|
||||
if (sstableKeyCompare(user_cmp, begin, smallest) <= 0) {
|
||||
assert(sstableKeyCompare(user_cmp, smallest, end) <= 0);
|
||||
} else {
|
||||
assert(user_cmp->Compare(flimit, user_begin) >= 0);
|
||||
// fprintf(stderr, "ExtendFileRangeOverlappingInterval\n%s - %s\n%s - %s\n%d %d\n",
|
||||
// begin ? begin->DebugString().c_str() : "(null)",
|
||||
// end ? end->DebugString().c_str() : "(null)",
|
||||
// smallest->DebugString().c_str(),
|
||||
// largest->DebugString().c_str(),
|
||||
// sstableKeyCompare(user_cmp, smallest, begin),
|
||||
// sstableKeyCompare(user_cmp, largest, begin));
|
||||
assert(sstableKeyCompare(user_cmp, begin, largest) <= 0);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -2227,8 +2285,8 @@ void VersionStorageInfo::ExtendFileRangeOverlappingInterval(
|
||||
// check backwards from 'mid' to lower indices
|
||||
for (int i = mid_index; i >= 0 ; i--) {
|
||||
const FdWithKeyRange* f = &files[i];
|
||||
const Slice file_limit = ExtractUserKey(f->largest_key);
|
||||
if (user_cmp->Compare(file_limit, user_begin) >= 0) {
|
||||
auto& largest = f->file_metadata->largest;
|
||||
if (sstableKeyCompare(user_cmp, begin, largest) <= 0) {
|
||||
*start_index = i;
|
||||
assert((count++, true));
|
||||
} else {
|
||||
@@ -2239,8 +2297,8 @@ void VersionStorageInfo::ExtendFileRangeOverlappingInterval(
|
||||
for (unsigned int i = mid_index+1;
|
||||
i < level_files_brief_[level].num_files; i++) {
|
||||
const FdWithKeyRange* f = &files[i];
|
||||
const Slice file_start = ExtractUserKey(f->smallest_key);
|
||||
if (user_cmp->Compare(file_start, user_end) <= 0) {
|
||||
auto& smallest = f->file_metadata->smallest;
|
||||
if (sstableKeyCompare(user_cmp, smallest, end) <= 0) {
|
||||
assert((count++, true));
|
||||
*end_index = i;
|
||||
} else {
|
||||
@@ -2258,39 +2316,36 @@ void VersionStorageInfo::ExtendFileRangeOverlappingInterval(
|
||||
// the clean range required.
|
||||
// Use FileLevel in searching, make it faster
|
||||
void VersionStorageInfo::ExtendFileRangeWithinInterval(
|
||||
int level, const Slice& user_begin, const Slice& user_end,
|
||||
int level, const InternalKey* begin, const InternalKey* end,
|
||||
unsigned int mid_index, int* start_index, int* end_index) const {
|
||||
assert(level != 0);
|
||||
const Comparator* user_cmp = user_comparator_;
|
||||
auto* user_cmp = user_comparator_;
|
||||
const FdWithKeyRange* files = level_files_brief_[level].files;
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
// assert that the file at mid_index is within the range
|
||||
assert(mid_index < level_files_brief_[level].num_files);
|
||||
const FdWithKeyRange* f = &files[mid_index];
|
||||
const Slice fstart = ExtractUserKey(f->smallest_key);
|
||||
const Slice flimit = ExtractUserKey(f->largest_key);
|
||||
assert(user_cmp->Compare(fstart, user_begin) >= 0 &&
|
||||
user_cmp->Compare(flimit, user_end) <= 0);
|
||||
auto& smallest = f->file_metadata->smallest;
|
||||
auto& largest = f->file_metadata->largest;
|
||||
assert(sstableKeyCompare(user_cmp, begin, smallest) <= 0 &&
|
||||
sstableKeyCompare(user_cmp, largest, end) <= 0);
|
||||
}
|
||||
#endif
|
||||
ExtendFileRangeOverlappingInterval(level, user_begin, user_end, mid_index,
|
||||
ExtendFileRangeOverlappingInterval(level, begin, end, mid_index,
|
||||
start_index, end_index);
|
||||
int left = *start_index;
|
||||
int right = *end_index;
|
||||
// shrink from left to right
|
||||
while (left <= right) {
|
||||
const Slice& first_key_in_range = ExtractUserKey(files[left].smallest_key);
|
||||
if (user_cmp->Compare(first_key_in_range, user_begin) < 0) {
|
||||
auto& smallest = files[left].file_metadata->smallest;
|
||||
if (sstableKeyCompare(user_cmp, begin, smallest) > 0) {
|
||||
left++;
|
||||
continue;
|
||||
}
|
||||
if (left > 0) { // If not first file
|
||||
const Slice& last_key_before =
|
||||
ExtractUserKey(files[left - 1].largest_key);
|
||||
if (user_cmp->Equal(first_key_in_range, last_key_before)) {
|
||||
// The first user key in range overlaps with the previous file's last
|
||||
// key
|
||||
auto& largest = files[left - 1].file_metadata->largest;
|
||||
if (sstableKeyCompare(user_cmp, smallest, largest) == 0) {
|
||||
left++;
|
||||
continue;
|
||||
}
|
||||
@@ -2299,16 +2354,15 @@ void VersionStorageInfo::ExtendFileRangeWithinInterval(
|
||||
}
|
||||
// shrink from right to left
|
||||
while (left <= right) {
|
||||
const Slice last_key_in_range = ExtractUserKey(files[right].largest_key);
|
||||
if (user_cmp->Compare(last_key_in_range, user_end) > 0) {
|
||||
auto& largest = files[right].file_metadata->largest;
|
||||
if (sstableKeyCompare(user_cmp, largest, end) > 0) {
|
||||
right--;
|
||||
continue;
|
||||
}
|
||||
if (right < static_cast<int>(level_files_brief_[level].num_files) -
|
||||
1) { // If not the last file
|
||||
const Slice first_key_after =
|
||||
ExtractUserKey(files[right + 1].smallest_key);
|
||||
if (user_cmp->Equal(last_key_in_range, first_key_after)) {
|
||||
auto& smallest = files[right + 1].file_metadata->smallest;
|
||||
if (sstableKeyCompare(user_cmp, smallest, largest) == 0) {
|
||||
// The last user key in range overlaps with the next file's first key
|
||||
right--;
|
||||
continue;
|
||||
@@ -2644,11 +2698,17 @@ struct VersionSet::ManifestWriter {
|
||||
bool done;
|
||||
InstrumentedCondVar cv;
|
||||
ColumnFamilyData* cfd;
|
||||
const MutableCFOptions mutable_cf_options;
|
||||
const autovector<VersionEdit*>& edit_list;
|
||||
|
||||
explicit ManifestWriter(InstrumentedMutex* mu, ColumnFamilyData* _cfd,
|
||||
const MutableCFOptions& cf_options,
|
||||
const autovector<VersionEdit*>& e)
|
||||
: done(false), cv(mu), cfd(_cfd), edit_list(e) {}
|
||||
: done(false),
|
||||
cv(mu),
|
||||
cfd(_cfd),
|
||||
mutable_cf_options(cf_options),
|
||||
edit_list(e) {}
|
||||
};
|
||||
|
||||
VersionSet::VersionSet(const std::string& dbname,
|
||||
@@ -2723,90 +2783,79 @@ void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
|
||||
v->next_->prev_ = v;
|
||||
}
|
||||
|
||||
Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<VersionEdit*>& edit_list,
|
||||
InstrumentedMutex* mu, Directory* db_directory,
|
||||
bool new_descriptor_log,
|
||||
const ColumnFamilyOptions* new_cf_options) {
|
||||
mu->AssertHeld();
|
||||
// num of edits
|
||||
auto num_edits = edit_list.size();
|
||||
if (num_edits == 0) {
|
||||
return Status::OK();
|
||||
} else if (num_edits > 1) {
|
||||
#ifndef NDEBUG
|
||||
// no group commits for column family add or drop
|
||||
for (auto& edit : edit_list) {
|
||||
assert(!edit->IsColumnFamilyManipulation());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
Status VersionSet::ProcessManifestWrites(
|
||||
std::deque<ManifestWriter>& writers, InstrumentedMutex* mu,
|
||||
Directory* db_directory, bool new_descriptor_log,
|
||||
const ColumnFamilyOptions* new_cf_options) {
|
||||
assert(!writers.empty());
|
||||
ManifestWriter& first_writer = writers.front();
|
||||
ManifestWriter* last_writer = &first_writer;
|
||||
|
||||
// column_family_data can be nullptr only if this is column_family_add.
|
||||
// in that case, we also need to specify ColumnFamilyOptions
|
||||
if (column_family_data == nullptr) {
|
||||
assert(num_edits == 1);
|
||||
assert(edit_list[0]->is_column_family_add_);
|
||||
assert(new_cf_options != nullptr);
|
||||
}
|
||||
|
||||
// queue our request
|
||||
ManifestWriter w(mu, column_family_data, edit_list);
|
||||
manifest_writers_.push_back(&w);
|
||||
while (!w.done && &w != manifest_writers_.front()) {
|
||||
w.cv.Wait();
|
||||
}
|
||||
if (w.done) {
|
||||
return w.status;
|
||||
}
|
||||
if (column_family_data != nullptr && column_family_data->IsDropped()) {
|
||||
// if column family is dropped by the time we get here, no need to write
|
||||
// anything to the manifest
|
||||
manifest_writers_.pop_front();
|
||||
// Notify new head of write queue
|
||||
if (!manifest_writers_.empty()) {
|
||||
manifest_writers_.front()->cv.Signal();
|
||||
}
|
||||
// we steal this code to also inform about cf-drop
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
assert(!manifest_writers_.empty());
|
||||
assert(manifest_writers_.front() == &first_writer);
|
||||
|
||||
autovector<VersionEdit*> batch_edits;
|
||||
Version* v = nullptr;
|
||||
std::unique_ptr<BaseReferencedVersionBuilder> builder_guard(nullptr);
|
||||
autovector<Version*> versions;
|
||||
autovector<const MutableCFOptions*> mutable_cf_options_ptrs;
|
||||
std::vector<std::unique_ptr<BaseReferencedVersionBuilder>> builder_guards;
|
||||
|
||||
// process all requests in the queue
|
||||
ManifestWriter* last_writer = &w;
|
||||
assert(!manifest_writers_.empty());
|
||||
assert(manifest_writers_.front() == &w);
|
||||
if (w.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// no group commits for column family add or drop
|
||||
LogAndApplyCFHelper(w.edit_list.front());
|
||||
batch_edits.push_back(w.edit_list.front());
|
||||
if (first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// No group commits for column family add or drop
|
||||
LogAndApplyCFHelper(first_writer.edit_list.front());
|
||||
batch_edits.push_back(first_writer.edit_list.front());
|
||||
} else {
|
||||
v = new Version(column_family_data, this, env_options_, mutable_cf_options,
|
||||
current_version_number_++);
|
||||
builder_guard.reset(new BaseReferencedVersionBuilder(column_family_data));
|
||||
auto* builder = builder_guard->version_builder();
|
||||
for (const auto& writer : manifest_writers_) {
|
||||
if (writer->edit_list.front()->IsColumnFamilyManipulation() ||
|
||||
writer->cfd->GetID() != column_family_data->GetID()) {
|
||||
auto it = manifest_writers_.cbegin();
|
||||
while (it != manifest_writers_.cend()) {
|
||||
if ((*it)->edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// no group commits for column family add or drop
|
||||
// also, group commits across column families are not supported
|
||||
break;
|
||||
}
|
||||
last_writer = writer;
|
||||
for (const auto& edit : writer->edit_list) {
|
||||
LogAndApplyHelper(column_family_data, builder, v, edit, mu);
|
||||
batch_edits.push_back(edit);
|
||||
last_writer = *(it++);
|
||||
assert(last_writer != nullptr);
|
||||
assert(last_writer->cfd != nullptr);
|
||||
if (last_writer->cfd != nullptr && last_writer->cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
// We do a linear search on versions because versions is small.
|
||||
// TODO(yanqin) maybe consider unordered_map
|
||||
Version* version = nullptr;
|
||||
VersionBuilder* builder = nullptr;
|
||||
for (int i = 0; i != static_cast<int>(versions.size()); ++i) {
|
||||
uint32_t cf_id = last_writer->cfd->GetID();
|
||||
if (versions[i]->cfd()->GetID() == cf_id) {
|
||||
version = versions[i];
|
||||
assert(!builder_guards.empty() &&
|
||||
builder_guards.size() == versions.size());
|
||||
builder = builder_guards[i]->version_builder();
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:SameColumnFamily", &cf_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (version == nullptr) {
|
||||
version = new Version(last_writer->cfd, this, env_options_,
|
||||
last_writer->mutable_cf_options,
|
||||
current_version_number_++);
|
||||
versions.push_back(version);
|
||||
mutable_cf_options_ptrs.push_back(&last_writer->mutable_cf_options);
|
||||
builder_guards.emplace_back(
|
||||
new BaseReferencedVersionBuilder(last_writer->cfd));
|
||||
builder = builder_guards.back()->version_builder();
|
||||
}
|
||||
assert(builder != nullptr); // make checker happy
|
||||
for (const auto& e : last_writer->edit_list) {
|
||||
LogAndApplyHelper(last_writer->cfd, builder, version, e, mu);
|
||||
batch_edits.push_back(e);
|
||||
}
|
||||
}
|
||||
builder->SaveTo(v->storage_info());
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
assert(!builder_guards.empty() &&
|
||||
builder_guards.size() == versions.size());
|
||||
auto* builder = builder_guards[i]->version_builder();
|
||||
builder->SaveTo(versions[i]->storage_info());
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize new descriptor log file if necessary by creating
|
||||
// a temporary file that contains a snapshot of the current version.
|
||||
uint64_t new_manifest_file_size = 0;
|
||||
Status s;
|
||||
|
||||
@@ -2821,39 +2870,39 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
}
|
||||
|
||||
if (new_descriptor_log) {
|
||||
// if we're writing out new snapshot make sure to persist max column family
|
||||
// if we are writing out new snapshot make sure to persist max column
|
||||
// family.
|
||||
if (column_family_set_->GetMaxColumnFamily() > 0) {
|
||||
w.edit_list.front()->SetMaxColumnFamily(
|
||||
first_writer.edit_list.front()->SetMaxColumnFamily(
|
||||
column_family_set_->GetMaxColumnFamily());
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock during expensive operations. New writes cannot get here
|
||||
// because &w is ensuring that all new writes get queued.
|
||||
{
|
||||
EnvOptions opt_env_opts = env_->OptimizeForManifestWrite(env_options_);
|
||||
// Before releasing mutex, make a copy of mutable_cf_options and pass to
|
||||
// `PrepareApply` to avoided a potential data race with backgroundflush
|
||||
MutableCFOptions mutable_cf_options_copy(mutable_cf_options);
|
||||
mu->Unlock();
|
||||
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifest");
|
||||
if (!w.edit_list.front()->IsColumnFamilyManipulation() &&
|
||||
this->GetColumnFamilySet()->get_table_cache()->GetCapacity() ==
|
||||
if (!first_writer.edit_list.front()->IsColumnFamilyManipulation() &&
|
||||
column_family_set_->get_table_cache()->GetCapacity() ==
|
||||
TableCache::kInfiniteCapacity) {
|
||||
// unlimited table cache. Pre-load table handle now.
|
||||
// Need to do it out of the mutex.
|
||||
builder_guard->version_builder()->LoadTableHandlers(
|
||||
column_family_data->internal_stats(),
|
||||
column_family_data->ioptions()->optimize_filters_for_hits,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
mutable_cf_options.prefix_extractor.get());
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
assert(!builder_guards.empty() &&
|
||||
builder_guards.size() == versions.size());
|
||||
assert(!mutable_cf_options_ptrs.empty() &&
|
||||
builder_guards.size() == versions.size());
|
||||
ColumnFamilyData* cfd = versions[i]->cfd_;
|
||||
builder_guards[i]->version_builder()->LoadTableHandlers(
|
||||
cfd->internal_stats(), cfd->ioptions()->optimize_filters_for_hits,
|
||||
true /* prefetch_index_and_filter_in_cache */,
|
||||
mutable_cf_options_ptrs[i]->prefix_extractor.get());
|
||||
}
|
||||
}
|
||||
|
||||
// This is fine because everything inside of this block is serialized --
|
||||
// only one thread can be here at the same time
|
||||
if (new_descriptor_log) {
|
||||
// create manifest file
|
||||
// create new manifest file
|
||||
ROCKS_LOG_INFO(db_options_->info_log, "Creating manifest %" PRIu64 "\n",
|
||||
pending_manifest_file_number_);
|
||||
unique_ptr<WritableFile> descriptor_file;
|
||||
@@ -2872,18 +2921,19 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
}
|
||||
}
|
||||
|
||||
if (!w.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
// This is cpu-heavy operations, which should be called outside mutex.
|
||||
v->PrepareApply(mutable_cf_options_copy, true);
|
||||
if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) {
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
versions[i]->PrepareApply(*mutable_cf_options_ptrs[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
// Write new record to MANIFEST log
|
||||
// Write new records to MANIFEST log
|
||||
if (s.ok()) {
|
||||
for (auto& e : batch_edits) {
|
||||
std::string record;
|
||||
if (!e->EncodeTo(&record)) {
|
||||
s = Status::Corruption(
|
||||
"Unable to Encode VersionEdit:" + e->DebugString(true));
|
||||
s = Status::Corruption("Unable to encode VersionEdit:" +
|
||||
e->DebugString(true));
|
||||
break;
|
||||
}
|
||||
TEST_KILL_RANDOM("VersionSet::LogAndApply:BeforeAddRecord",
|
||||
@@ -2897,7 +2947,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
s = SyncManifest(env_, db_options_, descriptor_log_->file());
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(db_options_->info_log, "MANIFEST write: %s\n",
|
||||
ROCKS_LOG_ERROR(db_options_->info_log, "MANIFEST write %s\n",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
@@ -2914,7 +2964,7 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
new_manifest_file_size = descriptor_log_->file()->GetFileSize();
|
||||
}
|
||||
|
||||
if (w.edit_list.front()->is_column_family_drop_) {
|
||||
if (first_writer.edit_list.front()->is_column_family_drop_) {
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:0");
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:1");
|
||||
TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:2");
|
||||
@@ -2925,25 +2975,24 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
mu->Lock();
|
||||
}
|
||||
|
||||
// Append the old mainfest file to the obsolete_manifests_ list to be deleted
|
||||
// Append the old manifest file to the obsolete_manifest_ list to be deleted
|
||||
// by PurgeObsoleteFiles later.
|
||||
if (s.ok() && new_descriptor_log) {
|
||||
obsolete_manifests_.emplace_back(
|
||||
DescriptorFileName("", manifest_file_number_));
|
||||
}
|
||||
|
||||
// Install the new version
|
||||
// Install the new versions
|
||||
if (s.ok()) {
|
||||
if (w.edit_list.front()->is_column_family_add_) {
|
||||
// no group commit on column family add
|
||||
if (first_writer.edit_list.front()->is_column_family_add_) {
|
||||
assert(batch_edits.size() == 1);
|
||||
assert(new_cf_options != nullptr);
|
||||
CreateColumnFamily(*new_cf_options, w.edit_list.front());
|
||||
} else if (w.edit_list.front()->is_column_family_drop_) {
|
||||
CreateColumnFamily(*new_cf_options, first_writer.edit_list.front());
|
||||
} else if (first_writer.edit_list.front()->is_column_family_drop_) {
|
||||
assert(batch_edits.size() == 1);
|
||||
column_family_data->SetDropped();
|
||||
if (column_family_data->Unref()) {
|
||||
delete column_family_data;
|
||||
first_writer.cfd->SetDropped();
|
||||
if (first_writer.cfd->Unref()) {
|
||||
delete first_writer.cfd;
|
||||
}
|
||||
} else {
|
||||
uint64_t max_log_number_in_batch = 0;
|
||||
@@ -2959,60 +3008,158 @@ Status VersionSet::LogAndApply(ColumnFamilyData* column_family_data,
|
||||
}
|
||||
}
|
||||
if (max_log_number_in_batch != 0) {
|
||||
assert(column_family_data->GetLogNumber() <= max_log_number_in_batch);
|
||||
column_family_data->SetLogNumber(max_log_number_in_batch);
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
ColumnFamilyData* cfd = versions[i]->cfd_;
|
||||
assert(cfd->GetLogNumber() <= max_log_number_in_batch);
|
||||
cfd->SetLogNumber(max_log_number_in_batch);
|
||||
}
|
||||
}
|
||||
|
||||
if (last_min_log_number_to_keep != 0) {
|
||||
// Should only be set in 2PC mode.
|
||||
MarkMinLogNumberToKeep2PC(last_min_log_number_to_keep);
|
||||
}
|
||||
|
||||
AppendVersion(column_family_data, v);
|
||||
for (int i = 0; i < static_cast<int>(versions.size()); ++i) {
|
||||
ColumnFamilyData* cfd = versions[i]->cfd_;
|
||||
AppendVersion(cfd, versions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
manifest_file_number_ = pending_manifest_file_number_;
|
||||
manifest_file_size_ = new_manifest_file_size;
|
||||
prev_log_number_ = w.edit_list.front()->prev_log_number_;
|
||||
prev_log_number_ = first_writer.edit_list.front()->prev_log_number_;
|
||||
} else {
|
||||
std::string version_edits;
|
||||
for (auto& e : batch_edits) {
|
||||
version_edits = version_edits + "\n" + e->DebugString(true);
|
||||
version_edits += ("\n" + e->DebugString(true));
|
||||
}
|
||||
ROCKS_LOG_ERROR(db_options_->info_log,
|
||||
"Error in committing version edit to MANIFEST: %s",
|
||||
version_edits.c_str());
|
||||
for (auto v : versions) {
|
||||
delete v;
|
||||
}
|
||||
ROCKS_LOG_ERROR(
|
||||
db_options_->info_log,
|
||||
"[%s] Error in committing version edit to MANIFEST: %s",
|
||||
column_family_data ? column_family_data->GetName().c_str() : "<null>",
|
||||
version_edits.c_str());
|
||||
delete v;
|
||||
if (new_descriptor_log) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log, "Deleting manifest %" PRIu64
|
||||
" current manifest %" PRIu64 "\n",
|
||||
ROCKS_LOG_INFO(db_options_->info_log,
|
||||
"Deleting manifest %" PRIu64 " current manifest %" PRIu64
|
||||
"\n",
|
||||
manifest_file_number_, pending_manifest_file_number_);
|
||||
descriptor_log_.reset();
|
||||
env_->DeleteFile(
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_));
|
||||
}
|
||||
}
|
||||
|
||||
pending_manifest_file_number_ = 0;
|
||||
|
||||
// wake up all the waiting writers
|
||||
while (true) {
|
||||
ManifestWriter* ready = manifest_writers_.front();
|
||||
manifest_writers_.pop_front();
|
||||
if (ready != &w) {
|
||||
ready->status = s;
|
||||
ready->done = true;
|
||||
bool need_signal = true;
|
||||
for (const auto& w : writers) {
|
||||
if (&w == ready) {
|
||||
need_signal = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ready->status = s;
|
||||
ready->done = true;
|
||||
if (need_signal) {
|
||||
ready->cv.Signal();
|
||||
}
|
||||
if (ready == last_writer) break;
|
||||
if (ready == last_writer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Notify new head of write queue
|
||||
if (!manifest_writers_.empty()) {
|
||||
manifest_writers_.front()->cv.Signal();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// 'datas' is gramatically incorrect. We still use this notation is to indicate
|
||||
// that this variable represents a collection of column_family_data.
|
||||
Status VersionSet::LogAndApply(
|
||||
const std::vector<ColumnFamilyData*>& column_family_datas,
|
||||
const std::vector<MutableCFOptions>& mutable_cf_options_list,
|
||||
const std::vector<autovector<VersionEdit*>>& edit_lists,
|
||||
InstrumentedMutex* mu, Directory* db_directory, bool new_descriptor_log,
|
||||
const ColumnFamilyOptions* new_cf_options) {
|
||||
mu->AssertHeld();
|
||||
int num_edits = 0;
|
||||
for (const auto& elist : edit_lists) {
|
||||
num_edits += static_cast<int>(elist.size());
|
||||
}
|
||||
if (num_edits == 0) {
|
||||
return Status::OK();
|
||||
} else if (num_edits > 1) {
|
||||
#ifndef NDEBUG
|
||||
for (const auto& edit_list : edit_lists) {
|
||||
for (const auto& edit : edit_list) {
|
||||
assert(!edit->IsColumnFamilyManipulation());
|
||||
}
|
||||
}
|
||||
#endif /* ! NDEBUG */
|
||||
}
|
||||
|
||||
int num_cfds = static_cast<int>(column_family_datas.size());
|
||||
if (num_cfds == 1 && column_family_datas[0] == nullptr) {
|
||||
assert(edit_lists.size() == 1 && edit_lists[0].size() == 1);
|
||||
assert(edit_lists[0][0]->is_column_family_add_);
|
||||
assert(new_cf_options != nullptr);
|
||||
}
|
||||
std::deque<ManifestWriter> writers;
|
||||
if (num_cfds > 0) {
|
||||
assert(static_cast<size_t>(num_cfds) == mutable_cf_options_list.size());
|
||||
assert(static_cast<size_t>(num_cfds) == edit_lists.size());
|
||||
}
|
||||
for (int i = 0; i < num_cfds; ++i) {
|
||||
writers.emplace_back(mu, column_family_datas[i], mutable_cf_options_list[i],
|
||||
edit_lists[i]);
|
||||
manifest_writers_.push_back(&writers[i]);
|
||||
}
|
||||
assert(!writers.empty());
|
||||
ManifestWriter& first_writer = writers.front();
|
||||
while (!first_writer.done && &first_writer != manifest_writers_.front()) {
|
||||
first_writer.cv.Wait();
|
||||
}
|
||||
if (first_writer.done) {
|
||||
// All non-CF-manipulation operations can be grouped together and committed
|
||||
// to MANIFEST. They should all have finished. The status code is stored in
|
||||
// the first manifest writer.
|
||||
#ifndef NDEBUG
|
||||
for (const auto& writer : writers) {
|
||||
assert(writer.done);
|
||||
}
|
||||
#endif /* !NDEBUG */
|
||||
return first_writer.status;
|
||||
}
|
||||
|
||||
int num_undropped_cfds = 0;
|
||||
for (auto cfd : column_family_datas) {
|
||||
// if cfd == nullptr, it is a column family add.
|
||||
if (cfd == nullptr || !cfd->IsDropped()) {
|
||||
++num_undropped_cfds;
|
||||
}
|
||||
}
|
||||
if (0 == num_undropped_cfds) {
|
||||
// TODO (yanqin) maybe use a different status code to denote column family
|
||||
// drop other than OK and ShutdownInProgress
|
||||
for (int i = 0; i != num_cfds; ++i) {
|
||||
manifest_writers_.pop_front();
|
||||
}
|
||||
// Notify new head of manifest write queue.
|
||||
if (!manifest_writers_.empty()) {
|
||||
manifest_writers_.front()->cv.Signal();
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
return ProcessManifestWrites(writers, mu, db_directory, new_descriptor_log,
|
||||
new_cf_options);
|
||||
}
|
||||
|
||||
void VersionSet::LogAndApplyCFHelper(VersionEdit* edit) {
|
||||
assert(edit->IsColumnFamilyManipulation());
|
||||
edit->SetNextFile(next_file_number_.load());
|
||||
@@ -3105,7 +3252,7 @@ Status VersionSet::Recover(
|
||||
return s;
|
||||
}
|
||||
manifest_file_reader.reset(
|
||||
new SequentialFileReader(std::move(manifest_file)));
|
||||
new SequentialFileReader(std::move(manifest_file), manifest_filename));
|
||||
}
|
||||
uint64_t current_manifest_file_size;
|
||||
s = env_->GetFileSize(manifest_filename, ¤t_manifest_file_size);
|
||||
@@ -3327,6 +3474,9 @@ Status VersionSet::Recover(
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (read_only) {
|
||||
cfd->table_cache()->SetTablesAreImmortal();
|
||||
}
|
||||
assert(cfd->initialized());
|
||||
auto builders_iter = builders.find(cfd->GetID());
|
||||
assert(builders_iter != builders.end());
|
||||
@@ -3415,7 +3565,7 @@ Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file)));
|
||||
file_reader.reset(new SequentialFileReader(std::move(file), dscname));
|
||||
}
|
||||
|
||||
std::map<uint32_t, std::string> column_family_names;
|
||||
@@ -3559,7 +3709,7 @@ Status VersionSet::DumpManifest(Options& options, std::string& dscname,
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
file_reader.reset(new SequentialFileReader(std::move(file)));
|
||||
file_reader.reset(new SequentialFileReader(std::move(file), dscname));
|
||||
}
|
||||
|
||||
bool have_prev_log_number = false;
|
||||
@@ -3933,8 +4083,8 @@ uint64_t VersionSet::ApproximateSize(Version* v, const FdWithKeyRange& f,
|
||||
// approximate offset of "key" within the table.
|
||||
TableReader* table_reader_ptr;
|
||||
InternalIterator* iter = v->cfd_->table_cache()->NewIterator(
|
||||
ReadOptions(), v->env_options_, v->cfd_->internal_comparator(), f.fd,
|
||||
nullptr /* range_del_agg */,
|
||||
ReadOptions(), v->env_options_, v->cfd_->internal_comparator(),
|
||||
*f.file_metadata, nullptr /* range_del_agg */,
|
||||
v->GetMutableCFOptions().prefix_extractor.get(), &table_reader_ptr);
|
||||
if (table_reader_ptr != nullptr) {
|
||||
result = table_reader_ptr->ApproximateOffsetOf(key);
|
||||
@@ -4014,12 +4164,12 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
for (size_t i = 0; i < flevel->num_files; i++) {
|
||||
list[num++] = cfd->table_cache()->NewIterator(
|
||||
read_options, env_options_compactions, cfd->internal_comparator(),
|
||||
flevel->files[i].fd, range_del_agg,
|
||||
*flevel->files[i].file_metadata, range_del_agg,
|
||||
c->mutable_cf_options()->prefix_extractor.get(),
|
||||
nullptr /* table_reader_ptr */,
|
||||
nullptr /* no per level latency histogram */,
|
||||
true /* for_compaction */, nullptr /* arena */,
|
||||
false /* skip_filters */, (int)which /* level */);
|
||||
false /* skip_filters */, static_cast<int>(which) /* level */);
|
||||
}
|
||||
} else {
|
||||
// Create concatenating iterator for the files from this level
|
||||
@@ -4030,7 +4180,7 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
false /* should_sample */,
|
||||
nullptr /* no per level latency histogram */,
|
||||
true /* for_compaction */, false /* skip_filters */,
|
||||
(int)which /* level */, range_del_agg);
|
||||
static_cast<int>(which) /* level */, range_del_agg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+40
-16
@@ -201,8 +201,8 @@ class VersionStorageInfo {
|
||||
|
||||
void GetOverlappingInputsRangeBinarySearch(
|
||||
int level, // level > 0
|
||||
const Slice& begin, // nullptr means before all keys
|
||||
const Slice& end, // nullptr means after all keys
|
||||
const InternalKey* begin, // nullptr means before all keys
|
||||
const InternalKey* end, // nullptr means after all keys
|
||||
std::vector<FileMetaData*>* inputs,
|
||||
int hint_index, // index of overlap file
|
||||
int* file_index, // return index of overlap file
|
||||
@@ -211,20 +211,20 @@ class VersionStorageInfo {
|
||||
|
||||
void ExtendFileRangeOverlappingInterval(
|
||||
int level,
|
||||
const Slice& begin, // nullptr means before all keys
|
||||
const Slice& end, // nullptr means after all keys
|
||||
unsigned int index, // start extending from this index
|
||||
int* startIndex, // return the startIndex of input range
|
||||
int* endIndex) // return the endIndex of input range
|
||||
const InternalKey* begin, // nullptr means before all keys
|
||||
const InternalKey* end, // nullptr means after all keys
|
||||
unsigned int index, // start extending from this index
|
||||
int* startIndex, // return the startIndex of input range
|
||||
int* endIndex) // return the endIndex of input range
|
||||
const;
|
||||
|
||||
void ExtendFileRangeWithinInterval(
|
||||
int level,
|
||||
const Slice& begin, // nullptr means before all keys
|
||||
const Slice& end, // nullptr means after all keys
|
||||
unsigned int index, // start extending from this index
|
||||
int* startIndex, // return the startIndex of input range
|
||||
int* endIndex) // return the endIndex of input range
|
||||
const InternalKey* begin, // nullptr means before all keys
|
||||
const InternalKey* end, // nullptr means after all keys
|
||||
unsigned int index, // start extending from this index
|
||||
int* startIndex, // return the startIndex of input range
|
||||
int* endIndex) // return the endIndex of input range
|
||||
const;
|
||||
|
||||
// Returns true iff some file in the specified level overlaps
|
||||
@@ -748,9 +748,11 @@ class VersionSet {
|
||||
InstrumentedMutex* mu, Directory* db_directory = nullptr,
|
||||
bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* column_family_options = nullptr) {
|
||||
autovector<VersionEdit*> edit_list;
|
||||
edit_list.push_back(edit);
|
||||
return LogAndApply(column_family_data, mutable_cf_options, edit_list, mu,
|
||||
std::vector<ColumnFamilyData*> cfds(1, column_family_data);
|
||||
std::vector<MutableCFOptions> mutable_cf_options_list(1,
|
||||
mutable_cf_options);
|
||||
std::vector<autovector<VersionEdit*>> edit_lists(1, {edit});
|
||||
return LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu,
|
||||
db_directory, new_descriptor_log, column_family_options);
|
||||
}
|
||||
// The batch version. If edit_list.size() > 1, caller must ensure that
|
||||
@@ -760,7 +762,24 @@ class VersionSet {
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const autovector<VersionEdit*>& edit_list, InstrumentedMutex* mu,
|
||||
Directory* db_directory = nullptr, bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* column_family_options = nullptr);
|
||||
const ColumnFamilyOptions* column_family_options = nullptr) {
|
||||
std::vector<ColumnFamilyData*> cfds(1, column_family_data);
|
||||
std::vector<MutableCFOptions> mutable_cf_options_list(1,
|
||||
mutable_cf_options);
|
||||
std::vector<autovector<VersionEdit*>> edit_lists(1, edit_list);
|
||||
return LogAndApply(cfds, mutable_cf_options_list, edit_lists, mu,
|
||||
db_directory, new_descriptor_log, column_family_options);
|
||||
}
|
||||
|
||||
// The across-multi-cf batch version. If edit_lists contain more than
|
||||
// 1 version edits, caller must ensure that no edit in the []list is column
|
||||
// family manipulation.
|
||||
Status LogAndApply(const std::vector<ColumnFamilyData*>& cfds,
|
||||
const std::vector<MutableCFOptions>& mutable_cf_options,
|
||||
const std::vector<autovector<VersionEdit*>>& edit_lists,
|
||||
InstrumentedMutex* mu, Directory* db_directory = nullptr,
|
||||
bool new_descriptor_log = false,
|
||||
const ColumnFamilyOptions* new_cf_options = nullptr);
|
||||
|
||||
// Recover the last saved descriptor from persistent storage.
|
||||
// If read_only == true, Recover() will not complain if some column families
|
||||
@@ -965,6 +984,11 @@ class VersionSet {
|
||||
ColumnFamilyData* CreateColumnFamily(const ColumnFamilyOptions& cf_options,
|
||||
VersionEdit* edit);
|
||||
|
||||
Status ProcessManifestWrites(std::deque<ManifestWriter>& writers,
|
||||
InstrumentedMutex* mu, Directory* db_directory,
|
||||
bool new_descriptor_log,
|
||||
const ColumnFamilyOptions* new_cf_options);
|
||||
|
||||
std::unique_ptr<ColumnFamilySet> column_family_set_;
|
||||
|
||||
Env* const env_;
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/version_set.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
@@ -137,6 +140,35 @@ class VersionStorageInfoTest : public testing::Test {
|
||||
f->num_deletions = 0;
|
||||
vstorage_.AddFile(level, f);
|
||||
}
|
||||
|
||||
void Add(int level, uint32_t file_number, const InternalKey& smallest,
|
||||
const InternalKey& largest, uint64_t file_size = 0) {
|
||||
assert(level < vstorage_.num_levels());
|
||||
FileMetaData* f = new FileMetaData;
|
||||
f->fd = FileDescriptor(file_number, 0, file_size);
|
||||
f->smallest = smallest;
|
||||
f->largest = largest;
|
||||
f->compensated_file_size = file_size;
|
||||
f->refs = 0;
|
||||
f->num_entries = 0;
|
||||
f->num_deletions = 0;
|
||||
vstorage_.AddFile(level, f);
|
||||
}
|
||||
|
||||
std::string GetOverlappingFiles(int level, const InternalKey& begin,
|
||||
const InternalKey& end) {
|
||||
std::vector<FileMetaData*> inputs;
|
||||
vstorage_.GetOverlappingInputs(level, &begin, &end, &inputs);
|
||||
|
||||
std::string result;
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
if (i > 0) {
|
||||
result += ",";
|
||||
}
|
||||
AppendNumberTo(&result, inputs[i]->fd.GetNumber());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(VersionStorageInfoTest, MaxBytesForLevelStatic) {
|
||||
@@ -259,6 +291,40 @@ TEST_F(VersionStorageInfoTest, EstimateLiveDataSize2) {
|
||||
ASSERT_EQ(4U, vstorage_.EstimateLiveDataSize());
|
||||
}
|
||||
|
||||
TEST_F(VersionStorageInfoTest, GetOverlappingInputs) {
|
||||
// Two files that overlap at the range deletion tombstone sentinel.
|
||||
Add(1, 1U, {"a", 0, kTypeValue}, {"b", kMaxSequenceNumber, kTypeRangeDeletion}, 1);
|
||||
Add(1, 2U, {"b", 0, kTypeValue}, {"c", 0, kTypeValue}, 1);
|
||||
// Two files that overlap at the same user key.
|
||||
Add(1, 3U, {"d", 0, kTypeValue}, {"e", kMaxSequenceNumber, kTypeValue}, 1);
|
||||
Add(1, 4U, {"e", 0, kTypeValue}, {"f", 0, kTypeValue}, 1);
|
||||
// Two files that do not overlap.
|
||||
Add(1, 5U, {"g", 0, kTypeValue}, {"h", 0, kTypeValue}, 1);
|
||||
Add(1, 6U, {"i", 0, kTypeValue}, {"j", 0, kTypeValue}, 1);
|
||||
vstorage_.UpdateNumNonEmptyLevels();
|
||||
vstorage_.GenerateLevelFilesBrief();
|
||||
|
||||
ASSERT_EQ("1,2", GetOverlappingFiles(
|
||||
1, {"a", 0, kTypeValue}, {"b", 0, kTypeValue}));
|
||||
ASSERT_EQ("1", GetOverlappingFiles(
|
||||
1, {"a", 0, kTypeValue}, {"b", kMaxSequenceNumber, kTypeRangeDeletion}));
|
||||
ASSERT_EQ("2", GetOverlappingFiles(
|
||||
1, {"b", kMaxSequenceNumber, kTypeValue}, {"c", 0, kTypeValue}));
|
||||
ASSERT_EQ("3,4", GetOverlappingFiles(
|
||||
1, {"d", 0, kTypeValue}, {"e", 0, kTypeValue}));
|
||||
ASSERT_EQ("3", GetOverlappingFiles(
|
||||
1, {"d", 0, kTypeValue}, {"e", kMaxSequenceNumber, kTypeRangeDeletion}));
|
||||
ASSERT_EQ("3,4", GetOverlappingFiles(
|
||||
1, {"e", kMaxSequenceNumber, kTypeValue}, {"f", 0, kTypeValue}));
|
||||
ASSERT_EQ("3,4", GetOverlappingFiles(
|
||||
1, {"e", 0, kTypeValue}, {"f", 0, kTypeValue}));
|
||||
ASSERT_EQ("5", GetOverlappingFiles(
|
||||
1, {"g", 0, kTypeValue}, {"h", 0, kTypeValue}));
|
||||
ASSERT_EQ("6", GetOverlappingFiles(
|
||||
1, {"i", 0, kTypeValue}, {"j", 0, kTypeValue}));
|
||||
}
|
||||
|
||||
|
||||
class FindLevelFileTest : public testing::Test {
|
||||
public:
|
||||
LevelFilesBrief file_level_;
|
||||
@@ -452,6 +518,127 @@ TEST_F(FindLevelFileTest, LevelOverlappingFiles) {
|
||||
ASSERT_TRUE(Overlaps("600", "700"));
|
||||
}
|
||||
|
||||
class ManifestWriterTest : public testing::Test {
|
||||
public:
|
||||
ManifestWriterTest()
|
||||
: env_(Env::Default()),
|
||||
dbname_(test::PerThreadDBPath("version_set_test")),
|
||||
db_options_(),
|
||||
mutable_cf_options_(cf_options_),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
versions_(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(std::make_shared<mock::MockTableFactory>()) {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
|
||||
// Create DB with 3 column families.
|
||||
void NewDB() {
|
||||
VersionEdit new_db;
|
||||
new_db.SetLogNumber(0);
|
||||
new_db.SetNextFile(2);
|
||||
new_db.SetLastSequence(0);
|
||||
|
||||
const std::vector<std::string> cf_names = {kDefaultColumnFamilyName,
|
||||
"alice", "bob"};
|
||||
const int kInitialNumOfCfs = static_cast<int>(cf_names.size());
|
||||
autovector<VersionEdit> new_cfs;
|
||||
uint64_t last_seq = 1;
|
||||
uint32_t cf_id = 1;
|
||||
for (int i = 1; i != kInitialNumOfCfs; ++i) {
|
||||
VersionEdit new_cf;
|
||||
new_cf.AddColumnFamily(cf_names[i]);
|
||||
new_cf.SetColumnFamily(cf_id++);
|
||||
new_cf.SetLogNumber(0);
|
||||
new_cf.SetNextFile(2);
|
||||
new_cf.SetLastSequence(last_seq++);
|
||||
new_cfs.emplace_back(new_cf);
|
||||
}
|
||||
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
unique_ptr<WritableFile> file;
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
ASSERT_OK(s);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options_));
|
||||
{
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
new_db.EncodeTo(&record);
|
||||
s = log.AddRecord(record);
|
||||
for (const auto& e : new_cfs) {
|
||||
e.EncodeTo(&record);
|
||||
s = log.AddRecord(record);
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
// Make "CURRENT" file point to the new manifest file.
|
||||
s = SetCurrentFile(env_, dbname_, 1, nullptr);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
for (const auto& cf_name : cf_names) {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
EXPECT_EQ(kInitialNumOfCfs,
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies());
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
cfds_.emplace_back(cfd);
|
||||
}
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
const std::string dbname_;
|
||||
EnvOptions env_options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
WriteController write_controller_;
|
||||
WriteBufferManager write_buffer_manager_;
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic<bool> shutting_down_;
|
||||
std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
|
||||
std::vector<ColumnFamilyData*> cfds_;
|
||||
};
|
||||
|
||||
TEST_F(ManifestWriterTest, SameColumnFamilyGroupCommit) {
|
||||
NewDB();
|
||||
const int kGroupSize = 5;
|
||||
std::vector<VersionEdit> edits(kGroupSize);
|
||||
std::vector<ColumnFamilyData*> cfds(kGroupSize, cfds_[0]);
|
||||
std::vector<MutableCFOptions> all_mutable_cf_options(kGroupSize,
|
||||
mutable_cf_options_);
|
||||
std::vector<autovector<VersionEdit*>> edit_lists(kGroupSize);
|
||||
for (int i = 0; i != kGroupSize; ++i) {
|
||||
edit_lists[i].emplace_back(&edits[i]);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::ProcessManifestWrites:SameColumnFamily", [&](void* arg) {
|
||||
uint32_t* cf_id = reinterpret_cast<uint32_t*>(arg);
|
||||
EXPECT_EQ(0, *cf_id);
|
||||
++count;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
mutex_.Lock();
|
||||
Status s =
|
||||
versions_->LogAndApply(cfds, all_mutable_cf_options, edit_lists, &mutex_);
|
||||
mutex_.Unlock();
|
||||
EXPECT_OK(s);
|
||||
EXPECT_EQ(kGroupSize - 1, count);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1
-1
@@ -444,7 +444,7 @@ Status WalManager::ReadFirstLine(const std::string& fname,
|
||||
Status status = env_->NewSequentialFile(
|
||||
fname, &file, env_->OptimizeForLogRead(env_options_));
|
||||
unique_ptr<SequentialFileReader> file_reader(
|
||||
new SequentialFileReader(std::move(file)));
|
||||
new SequentialFileReader(std::move(file), fname));
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user