mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 03:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fbc1cd5bc | ||
|
|
60f24d394d | ||
|
|
268a719a30 | ||
|
|
9791103976 | ||
|
|
4982688c21 | ||
|
|
ef5a37aca8 | ||
|
|
35148aca91 | ||
|
|
8c7c8b8dab | ||
|
|
2f583aed8f | ||
|
|
c4bbad4dfe | ||
|
|
dc33c1adaf | ||
|
|
c76cacc696 | ||
|
|
8c8586aa23 | ||
|
|
678690274d | ||
|
|
0762586067 | ||
|
|
57a6fb9e3a | ||
|
|
b9951ded37 | ||
|
|
f6c9c3bf1c | ||
|
|
2cf81e0a20 | ||
|
|
c757f5b4e3 | ||
|
|
9fbb68be17 | ||
|
|
b33c547b06 | ||
|
|
b897c3789b | ||
|
|
5879f8b62b | ||
|
|
ea75cdc493 | ||
|
|
2bee29729a | ||
|
|
37176a4a44 | ||
|
|
7603712a88 | ||
|
|
d56da8c112 | ||
|
|
befa6b8050 | ||
|
|
9577b92b55 | ||
|
|
94d91daddb | ||
|
|
0eb5b43b4f | ||
|
|
1bb704b6e0 | ||
|
|
64817ae604 | ||
|
|
fd0b4e0cf0 | ||
|
|
12b85c8ce9 | ||
|
|
a3aa44a716 | ||
|
|
32f66712c8 | ||
|
|
10478b98a5 | ||
|
|
e687ca79b4 | ||
|
|
2edc660e28 | ||
|
|
dce33f9443 | ||
|
|
fac8222bfe | ||
|
|
144e9f1e42 | ||
|
|
e691965558 |
No files matched your search
@@ -7,20 +7,27 @@ outputs:
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Extract FOLLY_COMMIT_HASH from Makefile
|
||||
- name: Extract FOLLY_MK_HASH
|
||||
id: extract-folly-hash
|
||||
shell: bash
|
||||
run: |
|
||||
FOLLY_COMMIT_HASH=$(grep '^FOLLY_COMMIT_HASH' Makefile | awk '{print $3}')
|
||||
echo "hash=$FOLLY_COMMIT_HASH" >> $GITHUB_OUTPUT
|
||||
FOLLY_MK_HASH=$(md5sum folly.mk | cut -d' ' -f1)
|
||||
echo "hash=$FOLLY_MK_HASH" >> $GITHUB_OUTPUT
|
||||
- name: Extract FOLLY_INSTALL_DIR
|
||||
id: extract-folly-install-dir
|
||||
shell: bash
|
||||
run: |
|
||||
FOLLY_INSTALL_DIR=$(cd third-party/folly && python3 build/fbcode_builder/getdeps.py show-inst-dir)
|
||||
echo "dir=$(echo $FOLLY_INSTALL_DIR | sed 's|installed/folly|installed|')" >> $GITHUB_OUTPUT
|
||||
- name: Cache folly build
|
||||
id: cache-folly-build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
# Cache the folly build directory
|
||||
path: /tmp/fbcode_builder_getdeps-Z__wZrocksdbZrocksdbZthird-partyZfollyZbuildZfbcode_builder-root/installed
|
||||
path: ${{ steps.extract-folly-install-dir.outputs.dir }}
|
||||
# Key is based on:
|
||||
# - OS and architecture
|
||||
# - The specific folly commit hash from Makefile
|
||||
# - The container image version to account for different compiler/library versions
|
||||
key: folly-build-${{ runner.os }}-${{ runner.arch }}-${{ steps.extract-folly-hash.outputs.hash }}-ubuntu22.1-v1
|
||||
# - The docker image, which may not always be specified/known
|
||||
# - Hash of folly.mk, which includes the folly repository commit hash
|
||||
# NOTE: this is still only intended for DEBUG folly builds
|
||||
key: folly-build-${{ runner.os }}-${{ runner.arch }}-${{ github.job_container.image }}-${{ steps.extract-folly-hash.outputs.hash }}
|
||||
@@ -5,5 +5,7 @@ runs:
|
||||
- name: Checkout folly sources
|
||||
run: |
|
||||
make checkout_folly
|
||||
echo "GETDEPS_USE_WGET=1" >> "$GITHUB_ENV"
|
||||
shell: bash
|
||||
- name: Install patchelf
|
||||
run: apt-get update -y && apt-get install -y patchelf
|
||||
shell: bash
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
- uses: "./.github/actions/build-folly"
|
||||
- run: LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-valgrind:
|
||||
build-linux-cmake-with-folly:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
@@ -68,7 +68,28 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: make V=1 -j32 valgrind_test
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- uses: "./.github/actions/cache-folly"
|
||||
id: cache-folly
|
||||
- uses: "./.github/actions/build-folly"
|
||||
with:
|
||||
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-release-with-folly:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: "DEBUG_LEVEL=0 make -j20 build_folly"
|
||||
- run: "USE_FOLLY=1 LIB_MODE=static DEBUG_LEVEL=0 V=1 make -j20 release"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 -DCMAKE_BUILD_TYPE=Release .. && make VERBOSE=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-windows-vs2022-avx2:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
@@ -117,7 +138,7 @@ jobs:
|
||||
- name: Build fuzzers
|
||||
run: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-cmake-with-folly-lite-no-test:
|
||||
build-linux-cmake-with-folly-lite:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
@@ -128,5 +149,5 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make V=1 -j20)"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make VERBOSE=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -92,24 +92,6 @@ jobs:
|
||||
which javac && javac -version
|
||||
mkdir build && cd build && cmake -DJNI=1 -DWITH_GFLAGS=OFF .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-cmake-with-folly:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- uses: "./.github/actions/cache-folly"
|
||||
id: cache-folly
|
||||
- uses: "./.github/actions/build-folly"
|
||||
with:
|
||||
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-make-with-folly:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
@@ -157,9 +139,9 @@ jobs:
|
||||
- uses: "./.github/actions/build-folly"
|
||||
with:
|
||||
cache-hit: ${{ steps.cache-folly.outputs.cache-hit }}
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-cmake-with-benchmark:
|
||||
build-linux-cmake-with-benchmark-no-thread-status:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
@@ -169,7 +151,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20
|
||||
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 -DCMAKE_CXX_FLAGS=-DNROCKSDB_THREAD_STATUS .. && make VERBOSE=1 -j20 && ctest -j20
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-encrypted_env-no_compression:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
@@ -372,7 +354,7 @@ jobs:
|
||||
- name: cmake generate project file
|
||||
run: ulimit -S -n `ulimit -H -n` && mkdir build && cd build && cmake -DWITH_GFLAGS=1 ..
|
||||
- name: Build tests
|
||||
run: cd build && make V=1 -j8
|
||||
run: cd build && make VERBOSE=1 -j8
|
||||
- name: Run shard 0 out of 4 test shards
|
||||
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 0,,4
|
||||
if: ${{ matrix.run_sharded_tests == 0 }}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: facebook/rocksdb/weekly
|
||||
on:
|
||||
schedule:
|
||||
- cron: 0 9 * * 0
|
||||
workflow_dispatch:
|
||||
permissions: {}
|
||||
jobs:
|
||||
build-linux-valgrind:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
timeout-minutes: 840
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: make V=1 -j20 valgrind_test
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -1,12 +1,14 @@
|
||||
# This file @generated by:
|
||||
#$ python3 buckifier/buckify_rocksdb.py
|
||||
# --> DO NOT EDIT MANUALLY <--
|
||||
# This file is a Facebook-specific integration for buck builds, so can
|
||||
# only be validated by Facebook employees.
|
||||
# This file is a Meta-specific integration for buck builds, so can
|
||||
# only be validated by Meta employees.
|
||||
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
|
||||
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
|
||||
|
||||
|
||||
oncall("rocksdb_point_of_contact")
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
@@ -4844,6 +4846,12 @@ cpp_unittest_wrapper(name="db_encryption_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_etc3_test",
|
||||
srcs=["db/db_etc3_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_flush_test",
|
||||
srcs=["db/db_flush_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
@@ -5194,12 +5202,6 @@ cpp_unittest_wrapper(name="import_column_family_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="index_builder_test",
|
||||
srcs=["table/block_based/index_builder_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="inlineskiplist_test",
|
||||
srcs=["memtable/inlineskiplist_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
+23
-2
@@ -80,6 +80,7 @@ if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING
|
||||
"Default BUILD_TYPE is ${default_build_type}" FORCE)
|
||||
endif()
|
||||
message(STATUS "CMAKE_BUILD_TYPE is set to ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
find_program(CCACHE_FOUND ccache)
|
||||
if(CCACHE_FOUND)
|
||||
@@ -132,7 +133,9 @@ else()
|
||||
option(WITH_GFLAGS "build with GFlags" ON)
|
||||
endif()
|
||||
set(GFLAGS_LIB)
|
||||
if(WITH_GFLAGS)
|
||||
# Skip all gflags detection and setup when USE_FOLLY or USE_COROUTINES is enabled
|
||||
# since Folly provides its own gflags (USE_COROUTINES automatically sets USE_FOLLY)
|
||||
if(WITH_GFLAGS AND NOT USE_FOLLY AND NOT USE_COROUTINES)
|
||||
# Config with namespace available since gflags 2.2.2
|
||||
option(GFLAGS_USE_TARGET_NAMESPACE "Use gflags import target with namespace." ON)
|
||||
find_package(gflags CONFIG)
|
||||
@@ -151,6 +154,9 @@ else()
|
||||
include_directories(${GFLAGS_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${GFLAGS_LIB})
|
||||
add_definitions(-DGFLAGS=1)
|
||||
elseif(WITH_GFLAGS AND (USE_FOLLY OR USE_COROUTINES))
|
||||
# Still set the DGFLAGS=1 define when using Folly since Folly provides gflags
|
||||
add_definitions(-DGFLAGS=1)
|
||||
endif()
|
||||
|
||||
if(WITH_SNAPPY)
|
||||
@@ -642,6 +648,12 @@ if(USE_FOLLY)
|
||||
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
|
||||
|
||||
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
|
||||
|
||||
# Fix gflags library name for debug builds
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-rpath=${GFLAGS_INST_PATH}/lib")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GFLAGS_INST_PATH}/lib/libgflags_debug.so.2.2")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_compile_definitions(USE_FOLLY FOLLY_NO_CONFIG HAVE_CXX11_ATOMIC)
|
||||
@@ -1082,12 +1094,21 @@ if(USE_FOLLY_LITE)
|
||||
third-party/folly/folly/synchronization/DistributedMutex.cpp
|
||||
third-party/folly/folly/synchronization/ParkingLot.cpp)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
|
||||
# Add boost to the include path
|
||||
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
|
||||
build/fbcode_builder/getdeps.py show-source-dir boost OUTPUT_VARIABLE
|
||||
BOOST_SOURCE_PATH)
|
||||
exec_program(ls ARGS -d ${BOOST_SOURCE_PATH}/boost* OUTPUT_VARIABLE
|
||||
BOOST_INCLUDE_DIR)
|
||||
include_directories(${BOOST_INCLUDE_DIR})
|
||||
# Add fmt to the include path
|
||||
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
|
||||
build/fbcode_builder/getdeps.py show-source-dir fmt OUTPUT_VARIABLE
|
||||
FMT_SOURCE_PATH)
|
||||
exec_program(ls ARGS -d ${FMT_SOURCE_PATH}/fmt*/include OUTPUT_VARIABLE
|
||||
FMT_INCLUDE_DIR)
|
||||
include_directories(${FMT_INCLUDE_DIR})
|
||||
|
||||
add_definitions(-DUSE_FOLLY -DFOLLY_NO_CONFIG)
|
||||
list(APPEND THIRDPARTY_LIBS glog)
|
||||
endif()
|
||||
@@ -1359,6 +1380,7 @@ if(WITH_TESTS)
|
||||
db/db_clip_test.cc
|
||||
db/db_dynamic_level_test.cc
|
||||
db/db_encryption_test.cc
|
||||
db/db_etc3_test.cc
|
||||
db/db_flush_test.cc
|
||||
db/db_inplace_update_test.cc
|
||||
db/db_io_failure_test.cc
|
||||
@@ -1452,7 +1474,6 @@ if(WITH_TESTS)
|
||||
table/block_based/block_based_table_reader_test.cc
|
||||
table/block_based/block_test.cc
|
||||
table/block_based/data_block_hash_index_test.cc
|
||||
table/block_based/index_builder_test.cc
|
||||
table/block_based/full_filter_block_test.cc
|
||||
table/block_based/partitioned_filter_block_test.cc
|
||||
table/cleanable_test.cc
|
||||
|
||||
+48
@@ -1,6 +1,54 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 10.9.1 (12/11/2025)
|
||||
### Bug Fixes
|
||||
* Fix resumable compaction incorrectly allowing resumption from a truncated range deletion that is not well handled currently.
|
||||
|
||||
## 10.9.0 (11/21/2025)
|
||||
### New Features
|
||||
* Added an auto-tuning feature for DB manifest file size that also (by default) improves the safety of existing configurations in case `max_manifest_file_size` is repeatedly exceeded. The new recommendation is to set `max_manifest_file_size` to something small like 1MB and tune `max_manifest_space_amp_pct` as needed to balance write amp and space amp in the manifest. Refer to comments on those options in `DBOptions` for details. Both options are (now) mutable.
|
||||
* Added a new API to support option migration for multiple column families
|
||||
* Added new option target_file_size_is_upper_bound that makes most compaction output SST files come close to the target file size without exceeding it, rather than commonly exceeding it by some fraction (current behavior). For now the new behavior is off by default, but we expect to enable it by default in the future.
|
||||
* Add a new option allow_trivial_move in CompactionOptions to allow CompactFiles to perform trivial move if possible. By default the flag of allow_trivial_move is false, so it preserve the original behavior.
|
||||
|
||||
### Public API Changes
|
||||
* To reduce risk of ODR violations or similar, `ROCKSDB_USING_THREAD_STATUS` has been removed from public headers and replaced with static `const bool ThreadStatus::kEnabled`. Some other uses of conditional compilation have been removed from public API headers to reduce risk of ODR violations or other issues.
|
||||
|
||||
### Behavior Changes
|
||||
* PosixWritableFile now repositions the seek pointer to the new end of file after a call to Truncate.
|
||||
* Updated standalone range deletion L0 file compaction behavior to avoid compacting with any newer L0 files (which is expensive and not useful).
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where compaction with range deletion can persist kTypeMaxValid in MANIFEST as file metadata. kTypeMaxValid is not supposed to be persisted and can change as new value types are introduced. This can cause a forward compatibility issue where older versions of RocksDB don't recognize kTypeMaxValid from newer versions. A new placeholder value type kTypeTruncatedRangeDeletionSentinel is also introduced to replace kTypeMaxValid when reading existing SST files' metadata from MANIFEST. This allows us to strengthen some checks to avoid using kTypeMaxValid in the future.
|
||||
* Fixed a bug where `DB::GetSortedWalFiles()` could hang when waiting for a purge operation that found nothing to do (potentially triggered by iterator release, flush, compaction, etc.).
|
||||
* Fixed a bug in MultiScan where `max_sequential_skip_in_iterations` could cause the iterator to seek backward to already-unpinned blocks when the same user key spans multiple data blocks, leading to assertion failures or seg fault.
|
||||
* Fixed a bug for `WAL_ttl_seconds > 0` use cases where the newest archived WAL files could be incorrectly deleted when the system clock moved backwards.
|
||||
|
||||
### Performance Improvements
|
||||
* Added optimization that allowed for the asynchronous prefetching of all data outlined in a multiscan iterator. This optimization was applied to the level iterator, which prefetches all data through each of the block-based iterators.
|
||||
|
||||
## 10.8.0 (10/21/2025)
|
||||
### New Features
|
||||
* Add kFSPrefetch to FSSupportedOps enum to allow file systems to indicate prefetch support capability, avoiding unnecessary prefetch system calls on file systems that don't support them.
|
||||
* Added experimental support `OpenAndCompactOptions::allow_resumption` for resumable compaction that persists progress during `OpenAndCompact()`, allowing interrupted compactions to resume from the last progress persitence. The default behavior is to not persist progress.
|
||||
|
||||
### Public API Changes
|
||||
* Allow specifying output temperature in CompactionOptions
|
||||
* Added `DB::FlushWAL(const FlushWALOptions&)` as an alternative to `DB::FlushWAL(bool sync)`, where `FlushWALOptions` includes a new `rate_limiter_priority` field (default `Env::IO_TOTAL`) that allows rate limiting and priority passing of manual WAL flush's IO operations.
|
||||
* The MultiScan API contract is updated. After a multi scan range got prepared with Prepare API call, the following seeks must seek the start of each prepared scan range in order. In addition, when limit is set, upper bound must be set to the same value of limit before each seek
|
||||
|
||||
### Behavior Changes
|
||||
* `kChangeTemperature` FIFO compaction will now honor `compaction_target_temp` to all levels regardless of `cf_options::last_level_temperature`
|
||||
* Allow UDIs with a non BytewiseComparator
|
||||
|
||||
### Bug Fixes
|
||||
* Fix incorrect MultiScan seek error status due to bugs in handling range limit falling between adjacent SST files key range.
|
||||
* Fix a bug in Page unpinning in MultiScan
|
||||
|
||||
### Performance Improvements
|
||||
* Fixed a performance regression in LZ4 compression that started in version 10.6.0
|
||||
|
||||
## 10.7.0 (09/19/2025)
|
||||
### New Features
|
||||
* Add the fail_if_no_udi_on_open flag in BlockBasedTableOption to control whether a missing user defined index block in a SST is a hard error or not.
|
||||
|
||||
@@ -446,83 +446,7 @@ else
|
||||
PLATFORM_CXXFLAGS += -isystem $(GTEST_DIR)
|
||||
endif
|
||||
|
||||
# This provides a Makefile simulation of a Meta-internal folly integration.
|
||||
# It is not validated for general use.
|
||||
#
|
||||
# USE_FOLLY links the build targets with libfolly.a. The latter could be
|
||||
# built using 'make build_folly', or built externally and specified in
|
||||
# the CXXFLAGS and EXTRA_LDFLAGS env variables. The build_detect_platform
|
||||
# script tries to detect if an external folly dependency has been specified.
|
||||
# If not, it exports FOLLY_PATH to the path of the installed Folly and
|
||||
# dependency libraries.
|
||||
#
|
||||
# USE_FOLLY_LITE cherry picks source files from Folly to include in the
|
||||
# RocksDB library. Its faster and has fewer dependencies on 3rd party
|
||||
# libraries, but with limited functionality. For example, coroutine
|
||||
# functionality is not available.
|
||||
ifeq ($(USE_FOLLY),1)
|
||||
ifeq ($(USE_FOLLY_LITE),1)
|
||||
$(error Please specify only one of USE_FOLLY and USE_FOLLY_LITE)
|
||||
endif
|
||||
ifneq ($(strip $(FOLLY_PATH)),)
|
||||
BOOST_PATH = $(shell (ls -d $(FOLLY_PATH)/../boost*))
|
||||
DBL_CONV_PATH = $(shell (ls -d $(FOLLY_PATH)/../double-conversion*))
|
||||
GFLAGS_PATH = $(shell (ls -d $(FOLLY_PATH)/../gflags*))
|
||||
GLOG_PATH = $(shell (ls -d $(FOLLY_PATH)/../glog*))
|
||||
LIBEVENT_PATH = $(shell (ls -d $(FOLLY_PATH)/../libevent*))
|
||||
XZ_PATH = $(shell (ls -d $(FOLLY_PATH)/../xz*))
|
||||
LIBSODIUM_PATH = $(shell (ls -d $(FOLLY_PATH)/../libsodium*))
|
||||
FMT_PATH = $(shell (ls -d $(FOLLY_PATH)/../fmt*))
|
||||
|
||||
# For some reason, glog and fmt libraries are under either lib or lib64
|
||||
GLOG_LIB_PATH = $(shell (ls -d $(GLOG_PATH)/lib*))
|
||||
FMT_LIB_PATH = $(shell (ls -d $(FMT_PATH)/lib*))
|
||||
|
||||
# AIX: pre-defined system headers are surrounded by an extern "C" block
|
||||
ifeq ($(PLATFORM), OS_AIX)
|
||||
PLATFORM_CCFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
|
||||
PLATFORM_CXXFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
|
||||
else
|
||||
PLATFORM_CCFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
|
||||
PLATFORM_CXXFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
|
||||
endif
|
||||
|
||||
# Add -ldl at the end as gcc resolves a symbol in a library by searching only in libraries specified later
|
||||
# in the command line
|
||||
PLATFORM_LDFLAGS += $(FOLLY_PATH)/lib/libfolly.a $(BOOST_PATH)/lib/libboost_context.a $(BOOST_PATH)/lib/libboost_filesystem.a $(BOOST_PATH)/lib/libboost_atomic.a $(BOOST_PATH)/lib/libboost_program_options.a $(BOOST_PATH)/lib/libboost_regex.a $(BOOST_PATH)/lib/libboost_system.a $(BOOST_PATH)/lib/libboost_thread.a $(DBL_CONV_PATH)/lib/libdouble-conversion.a $(FMT_LIB_PATH)/libfmt.a $(GLOG_LIB_PATH)/libglog.so $(GFLAGS_PATH)/lib/libgflags.so.2.2 $(LIBEVENT_PATH)/lib/libevent-2.1.so -ldl
|
||||
PLATFORM_LDFLAGS += -Wl,-rpath=$(GFLAGS_PATH)/lib -Wl,-rpath=$(GLOG_LIB_PATH) -Wl,-rpath=$(LIBEVENT_PATH)/lib -Wl,-rpath=$(LIBSODIUM_PATH)/lib -Wl,-rpath=$(LIBEVENT_PATH)/lib
|
||||
endif
|
||||
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
endif
|
||||
|
||||
ifeq ($(USE_FOLLY_LITE),1)
|
||||
# Path to the Folly source code and include files
|
||||
FOLLY_DIR = ./third-party/folly
|
||||
ifneq ($(strip $(BOOST_SOURCE_PATH)),)
|
||||
BOOST_INCLUDE = $(shell (ls -d $(BOOST_SOURCE_PATH)/boost*/))
|
||||
# AIX: pre-defined system headers are surrounded by an extern "C" block
|
||||
ifeq ($(PLATFORM), OS_AIX)
|
||||
PLATFORM_CCFLAGS += -I$(BOOST_INCLUDE)
|
||||
PLATFORM_CXXFLAGS += -I$(BOOST_INCLUDE)
|
||||
else
|
||||
PLATFORM_CCFLAGS += -isystem $(BOOST_INCLUDE)
|
||||
PLATFORM_CXXFLAGS += -isystem $(BOOST_INCLUDE)
|
||||
endif
|
||||
endif # BOOST_SOURCE_PATH
|
||||
# AIX: pre-defined system headers are surrounded by an extern "C" block
|
||||
ifeq ($(PLATFORM), OS_AIX)
|
||||
PLATFORM_CCFLAGS += -I$(FOLLY_DIR)
|
||||
PLATFORM_CXXFLAGS += -I$(FOLLY_DIR)
|
||||
else
|
||||
PLATFORM_CCFLAGS += -isystem $(FOLLY_DIR)
|
||||
PLATFORM_CXXFLAGS += -isystem $(FOLLY_DIR)
|
||||
endif
|
||||
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
# TODO: fix linking with fbcode compiler config
|
||||
PLATFORM_LDFLAGS += -lglog
|
||||
endif
|
||||
include folly.mk
|
||||
|
||||
ifdef TEST_CACHE_LINE_SIZE
|
||||
PLATFORM_CCFLAGS += -DTEST_CACHE_LINE_SIZE=$(TEST_CACHE_LINE_SIZE)
|
||||
@@ -683,6 +607,7 @@ am__v_CCH_1 =
|
||||
%.h.pub: %.h # .h.pub not actually created, so re-checked on each invocation
|
||||
$(AM_V_CCH) cd include/ && echo '#include "$(patsubst include/%,%,$<)"' | \
|
||||
$(CXX) -std=$(or $(ROCKSDB_CXX_STANDARD),c++20) -I. -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
|
||||
build_tools/check-public-header.sh $<
|
||||
|
||||
check-headers: $(HEADER_OK_FILES)
|
||||
|
||||
@@ -1496,6 +1421,9 @@ db_test: $(OBJ_DIR)/db/db_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
db_test2: $(OBJ_DIR)/db/db_test2.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_etc3_test: $(OBJ_DIR)/db/db_etc3_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
compression_test: $(OBJ_DIR)/util/compression_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1739,9 +1667,6 @@ block_test: $(OBJ_DIR)/table/block_based/block_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
data_block_hash_index_test: $(OBJ_DIR)/table/block_based/data_block_hash_index_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
index_builder_test: $(OBJ_DIR)/table/block_based/index_builder_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
inlineskiplist_test: $(OBJ_DIR)/memtable/inlineskiplist_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -2495,42 +2420,6 @@ commit_prereq:
|
||||
false # J=$(J) build_tools/precommit_checker.py unit clang_unit release clang_release tsan asan ubsan lite unit_non_shm
|
||||
# $(MAKE) clean && $(MAKE) jclean && $(MAKE) rocksdbjava;
|
||||
|
||||
FOLLY_COMMIT_HASH = e95383b7c8b5b1e46cf47acf2f317d54f93c8268
|
||||
|
||||
# For public CI runs, checkout folly in a way that can build with RocksDB.
|
||||
# This is mostly intended as a test-only simulation of Meta-internal folly
|
||||
# integration.
|
||||
checkout_folly:
|
||||
if [ -e third-party/folly ]; then \
|
||||
cd third-party/folly && ${GIT_COMMAND} fetch origin; \
|
||||
else \
|
||||
cd third-party && ${GIT_COMMAND} clone https://github.com/facebook/folly.git; \
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard $(FOLLY_COMMIT_HASH)
|
||||
@# Apparently missing include
|
||||
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
|
||||
@# Warning-as-error on memcpy
|
||||
perl -pi -e 's/memcpy.&ptr/memcpy((void*)&ptr/' third-party/folly/folly/lang/Exception.cpp
|
||||
@# const mismatch
|
||||
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
|
||||
@# NOTE: boost source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on boost headers
|
||||
cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py fetch boost
|
||||
|
||||
CXX_M_FLAGS = $(filter -m%, $(CXXFLAGS))
|
||||
|
||||
build_folly:
|
||||
FOLLY_INST_PATH=`cd third-party/folly; $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
|
||||
if [ "$$FOLLY_INST_PATH" ]; then \
|
||||
rm -rf $${FOLLY_INST_PATH}/../../*; \
|
||||
else \
|
||||
echo "Please run checkout_folly first"; \
|
||||
false; \
|
||||
fi
|
||||
cd third-party/folly && \
|
||||
CXXFLAGS=" $(CXX_M_FLAGS) -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build size testing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -135,6 +135,9 @@ def generate_buck(repo_path, deps_map):
|
||||
|
||||
BUCK = TARGETSBuilder("%s/BUCK" % repo_path, extra_argv)
|
||||
|
||||
# Add oncall("rocksdb_point_of_contact") at the top
|
||||
BUCK.add_oncall("rocksdb_point_of_contact")
|
||||
|
||||
# rocksdb_lib
|
||||
BUCK.add_library(
|
||||
"rocksdb_lib",
|
||||
|
||||
@@ -45,6 +45,11 @@ class TARGETSBuilder:
|
||||
self.total_bin = 0
|
||||
self.total_test = 0
|
||||
self.tests_cfg = ""
|
||||
|
||||
def add_oncall(self, oncall):
|
||||
with open(self.path, "ab") as targets_file:
|
||||
targets_file.write(targets_cfg.oncall_template.format(name=oncall).encode("utf-8"))
|
||||
|
||||
|
||||
def add_library(
|
||||
self,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)
|
||||
# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
rocksdb_target_header_template = """# This file \100generated by:
|
||||
#$ python3 buckifier/buckify_rocksdb.py{extra_argv}
|
||||
# --> DO NOT EDIT MANUALLY <--
|
||||
# This file is a Facebook-specific integration for buck builds, so can
|
||||
# only be validated by Facebook employees.
|
||||
# This file is a Meta-specific integration for buck builds, so can
|
||||
# only be validated by Meta employees.
|
||||
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
|
||||
load("@fbcode_macros//build_defs:export_files.bzl", "export_file")
|
||||
|
||||
@@ -41,3 +43,8 @@ fancy_bench_wrapper(suite_name="{name}", binary_to_bench_to_metric_list_map={ben
|
||||
export_file_template = """
|
||||
export_file(name = "{name}")
|
||||
"""
|
||||
|
||||
|
||||
oncall_template = """
|
||||
oncall("{name}")
|
||||
"""
|
||||
@@ -311,7 +311,8 @@ EOF
|
||||
EOF
|
||||
then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# Hack: don't link extra gflags assuming it comes with folly
|
||||
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# check if namespace is gflags
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
@@ -320,7 +321,8 @@ EOF
|
||||
EOF
|
||||
then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# Hack: don't link extra gflags assuming it comes with folly
|
||||
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# check if namespace is google
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
@@ -772,6 +774,7 @@ fi
|
||||
if [ "$USE_FOLLY_LITE" ]; then
|
||||
if [ "$FOLLY_DIR" ]; then
|
||||
BOOST_SOURCE_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-source-dir boost`
|
||||
FMT_SOURCE_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-source-dir fmt`
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -816,6 +819,7 @@ echo "FIND=$FIND" >> "$OUTPUT"
|
||||
echo "WATCH=$WATCH" >> "$OUTPUT"
|
||||
echo "FOLLY_PATH=$FOLLY_PATH" >> "$OUTPUT"
|
||||
echo "BOOST_SOURCE_PATH=$BOOST_SOURCE_PATH" >> "$OUTPUT"
|
||||
echo "FMT_SOURCE_PATH=$FMT_SOURCE_PATH" >> "$OUTPUT"
|
||||
|
||||
# This will enable some related identifiers for the preprocessor
|
||||
if test -n "$JEMALLOC"; then
|
||||
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
|
||||
#
|
||||
# Check for some simple mistakes in public headers (on the command line)
|
||||
# that should prevent commit or push
|
||||
|
||||
BAD=""
|
||||
|
||||
# Look for potential for ODR violations caused by public headers depending on
|
||||
# build parameters that could vary between RocksDB build and application build.
|
||||
# * Cases like LUA, ROCKSDB_NAMESPACE, and ROCKSDB_ASSERT_STATUS_CHECKED are
|
||||
# intentional, hard to avoid. (We expect definitions to change and the user
|
||||
# should also.)
|
||||
# * Cases like _WIN32, OS_WIN, and __cplusplus are essentially ODR-safe.
|
||||
# * Cases like
|
||||
# #ifdef BLAH // ODR-SAFE
|
||||
# #undef BLAH
|
||||
# #endif
|
||||
# that should not cause ODR violations can be exempted with the ODR-SAFE
|
||||
# marker recognized here.
|
||||
|
||||
grep -nHE '^#if' -- "$@" | grep -vE 'ROCKSDB_NAMESPACE|ROCKSDB_ASSERT_STATUS_CHECKED|LUA|_WIN32|OS_WIN|ODR-SAFE|__cplusplus|ROCKSDB_DLL|ROCKSDB_LIBRARY_EXPORTS'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ #if in public API could cause an ODR violation."
|
||||
echo " Add // ODR-SAFE if verified safe."
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
if [ "$BAD" ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -148,6 +148,70 @@ else
|
||||
echo "Checking format of uncommitted changes..."
|
||||
fi
|
||||
|
||||
# Check for missing copyright in new files
|
||||
echo "Checking for copyright headers in new files..."
|
||||
|
||||
# Get list of new files (added, not just modified)
|
||||
if [ -z "$uncommitted_code" ]; then
|
||||
# Post-commit: check files added since merge base
|
||||
new_files=$(git diff --name-only --diff-filter=A "$FORMAT_UPSTREAM_MERGE_BASE" -- '*.h' '*.cc' '*.py' $EXCLUDE)
|
||||
else
|
||||
# Pre-commit: check staged new files
|
||||
new_files=$(git diff --name-only --diff-filter=A --cached HEAD -- '*.h' '*.cc' '*.py' $EXCLUDE)
|
||||
fi
|
||||
|
||||
if [ -n "$new_files" ]; then
|
||||
files_missing_copyright=""
|
||||
|
||||
for file in $new_files; do
|
||||
if [ -f "$file" ]; then
|
||||
# Check if file is missing copyright
|
||||
# For .py files, check for Python-style comment
|
||||
# For .h and .cc files, check for C++-style comment
|
||||
if [[ "$file" == *.py ]]; then
|
||||
if ! grep -q "Copyright (c) Meta Platforms, Inc. and affiliates" "$file"; then
|
||||
files_missing_copyright="$files_missing_copyright $file"
|
||||
# Add copyright header to Python file
|
||||
temp_file=$(mktemp)
|
||||
{
|
||||
echo "# Copyright (c) Meta Platforms, Inc. and affiliates."
|
||||
echo "# This source code is licensed under both the GPLv2 (found in the COPYING file in the root directory)"
|
||||
echo "# and the Apache 2.0 License (found in the LICENSE.Apache file in the root directory)."
|
||||
echo
|
||||
cat "$file"
|
||||
} > "$temp_file"
|
||||
mv "$temp_file" "$file"
|
||||
echo "Added copyright header to $file"
|
||||
fi
|
||||
elif [[ "$file" == *.h ]] || [[ "$file" == *.cc ]]; then
|
||||
if ! grep -q "Copyright (c) Meta Platforms, Inc. and affiliates" "$file"; then
|
||||
files_missing_copyright="$files_missing_copyright $file"
|
||||
# Add copyright header to C++ file
|
||||
temp_file=$(mktemp)
|
||||
{
|
||||
echo "// Copyright (c) Meta Platforms, Inc. and affiliates. "
|
||||
echo "// This source code is licensed under both the GPLv2 (found in the "
|
||||
echo "// COPYING file in the root directory) and Apache 2.0 License "
|
||||
echo "// (found in the LICENSE.Apache file in the root directory)."
|
||||
echo
|
||||
cat "$file"
|
||||
} > "$temp_file"
|
||||
mv "$temp_file" "$file"
|
||||
echo "Added copyright header to $file"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$files_missing_copyright" ]; then
|
||||
echo "Copyright headers were added to new files."
|
||||
else
|
||||
echo "All new files have copyright headers."
|
||||
fi
|
||||
else
|
||||
echo "No new files to check for copyright headers."
|
||||
fi
|
||||
|
||||
if [ -z "$diffs" ]
|
||||
then
|
||||
echo "Nothing needs to be reformatted!"
|
||||
|
||||
Vendored
+1
-1
@@ -119,7 +119,7 @@ DEFINE_uint32(seed, 0, "Hashing/random seed to use. 0 = choose at random");
|
||||
DEFINE_string(secondary_cache_uri, "",
|
||||
"Full URI for creating a custom secondary cache object");
|
||||
|
||||
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
|
||||
DEFINE_string(cache_type, "hyper_clock_cache", "Type of block cache.");
|
||||
|
||||
DEFINE_bool(use_jemalloc_no_dump_allocator, false,
|
||||
"Whether to use JemallocNoDumpAllocator");
|
||||
|
||||
@@ -635,10 +635,11 @@ static bool SaveError(char** errptr, const Status& s) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copies str to a new malloc()-ed buffer. The buffer is not NUL terminated.
|
||||
static char* CopyString(const std::string& str) {
|
||||
char* result = reinterpret_cast<char*>(malloc(sizeof(char) * str.size()));
|
||||
memcpy(result, str.data(), sizeof(char) * str.size());
|
||||
// Helper function to copy string data to a malloc'd buffer
|
||||
// Works with std::string, Slice, and PinnableSlice through implicit conversion
|
||||
static inline char* CopyString(const Slice& slice) {
|
||||
char* result = reinterpret_cast<char*>(malloc(slice.size()));
|
||||
memcpy(result, slice.data(), slice.size());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1440,11 +1441,14 @@ char* rocksdb_get(rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t keylen, size_t* vallen,
|
||||
char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s = db->rep->Get(options->rep, Slice(key, keylen), &tmp);
|
||||
// Use PinnableSlice to avoid unnecessary copy
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
|
||||
Slice(key, keylen), &pinnable_val);
|
||||
if (s.ok()) {
|
||||
*vallen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vallen = pinnable_val.size();
|
||||
// Only one copy: from PinnableSlice to malloc'd buffer
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vallen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -1459,12 +1463,14 @@ char* rocksdb_get_cf(rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t keylen, size_t* vallen,
|
||||
char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s =
|
||||
db->rep->Get(options->rep, column_family->rep, Slice(key, keylen), &tmp);
|
||||
// Use PinnableSlice to avoid unnecessary copy
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
|
||||
&pinnable_val);
|
||||
if (s.ok()) {
|
||||
*vallen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vallen = pinnable_val.size();
|
||||
// Only one copy: from PinnableSlice to malloc'd buffer
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vallen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -1537,12 +1543,17 @@ void rocksdb_multi_get(rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size array)
|
||||
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses = db->rep->MultiGet(options->rep, keys, &values);
|
||||
// Use PinnableSlice to avoid unnecessary allocations
|
||||
auto cfh = db->rep->DefaultColumnFamily();
|
||||
std::vector<PinnableSlice> values(num_keys);
|
||||
std::vector<Status> statuses(num_keys);
|
||||
db->rep->MultiGet(options->rep, cfh, num_keys, keys.get(), values.data(),
|
||||
statuses.data());
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
@@ -1567,10 +1578,13 @@ void rocksdb_multi_get_with_ts(rocksdb_t* db,
|
||||
char** values_list, size_t* values_list_sizes,
|
||||
char** timestamp_list,
|
||||
size_t* timestamp_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
// Use unique_ptr for efficiency
|
||||
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
// Note: MultiGet with timestamps only has vector-based API
|
||||
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<std::string> timestamps(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
@@ -1602,15 +1616,19 @@ void rocksdb_multi_get_cf(
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
std::vector<ColumnFamilyHandle*> cfs(num_keys);
|
||||
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size
|
||||
// arrays)
|
||||
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
|
||||
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
cfs[i] = column_families[i]->rep;
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
db->rep->MultiGet(options->rep, cfs, keys, &values);
|
||||
// Use PinnableSlice to avoid unnecessary allocations
|
||||
std::vector<PinnableSlice> values(num_keys);
|
||||
std::vector<Status> statuses(num_keys);
|
||||
db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
|
||||
values.data(), statuses.data());
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
@@ -1635,16 +1653,20 @@ void rocksdb_multi_get_cf_with_ts(
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** timestamps_list,
|
||||
size_t* timestamps_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
std::vector<ColumnFamilyHandle*> cfs(num_keys);
|
||||
// Use unique_ptr for efficiency (avoids vector overhead for fixed-size
|
||||
// arrays)
|
||||
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
|
||||
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
cfs[i] = column_families[i]->rep;
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
// Use PinnableSlice to avoid unnecessary allocations
|
||||
std::vector<PinnableSlice> values(num_keys);
|
||||
std::vector<std::string> timestamps(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
db->rep->MultiGet(options->rep, cfs, keys, &values, ×tamps);
|
||||
std::vector<Status> statuses(num_keys);
|
||||
db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
|
||||
values.data(), timestamps.data(), statuses.data());
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
@@ -1703,6 +1725,41 @@ void rocksdb_batched_multi_get_cf(rocksdb_t* db,
|
||||
delete[] statuses;
|
||||
}
|
||||
|
||||
// Batched MultiGet that takes pre-built Slice array, avoiding key conversion
|
||||
// overhead
|
||||
void rocksdb_batched_multi_get_cf_slice(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, size_t num_keys,
|
||||
const rocksdb_slice_t* keys_list, rocksdb_pinnableslice_t** values,
|
||||
char** errs, const bool sorted_input) {
|
||||
PinnableSlice* value_slices = new PinnableSlice[num_keys];
|
||||
Status* statuses = new Status[num_keys];
|
||||
|
||||
// Cast rocksdb_slice_t* to Slice* - they have identical memory layout
|
||||
const Slice* key_slices = reinterpret_cast<const Slice*>(keys_list);
|
||||
|
||||
db->rep->MultiGet(options->rep, column_family->rep, num_keys, key_slices,
|
||||
value_slices, statuses, sorted_input);
|
||||
|
||||
for (size_t i = 0; i < num_keys; ++i) {
|
||||
if (statuses[i].ok()) {
|
||||
values[i] = new (rocksdb_pinnableslice_t);
|
||||
values[i]->rep = std::move(value_slices[i]);
|
||||
errs[i] = nullptr;
|
||||
} else {
|
||||
values[i] = nullptr;
|
||||
if (!statuses[i].IsNotFound()) {
|
||||
errs[i] = strdup(statuses[i].ToString().c_str());
|
||||
} else {
|
||||
errs[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete[] value_slices;
|
||||
delete[] statuses;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_key_may_exist(rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t key_len,
|
||||
@@ -2123,6 +2180,32 @@ void rocksdb_iter_get_error(const rocksdb_iterator_t* iter, char** errptr) {
|
||||
SaveError(errptr, iter->rep->status());
|
||||
}
|
||||
|
||||
// Iterator functions that return rocksdb_slice_t directly for better
|
||||
// performance
|
||||
rocksdb_slice_t rocksdb_iter_key_slice(const rocksdb_iterator_t* iter) {
|
||||
Slice s = iter->rep->key();
|
||||
rocksdb_slice_t result;
|
||||
result.data = s.data();
|
||||
result.size = s.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_slice_t rocksdb_iter_value_slice(const rocksdb_iterator_t* iter) {
|
||||
Slice s = iter->rep->value();
|
||||
rocksdb_slice_t result;
|
||||
result.data = s.data();
|
||||
result.size = s.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_slice_t rocksdb_iter_timestamp_slice(const rocksdb_iterator_t* iter) {
|
||||
Slice s = iter->rep->timestamp();
|
||||
rocksdb_slice_t result;
|
||||
result.data = s.data();
|
||||
result.size = s.size();
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_iter_refresh(const rocksdb_iterator_t* iter, char** errptr) {
|
||||
SaveError(errptr, iter->rep->Refresh());
|
||||
}
|
||||
@@ -2178,16 +2261,18 @@ void rocksdb_writebatch_putv(rocksdb_writebatch_t* b, int num_keys,
|
||||
const size_t* keys_list_sizes, int num_values,
|
||||
const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr instead of vector to avoid overhead
|
||||
// Safe because WriteBatch::Put immediately copies the data
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep.Put(SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
b->rep.Put(SliceParts(key_slices.get(), num_keys),
|
||||
SliceParts(value_slices.get(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_putv_cf(rocksdb_writebatch_t* b,
|
||||
@@ -2196,16 +2281,18 @@ void rocksdb_writebatch_putv_cf(rocksdb_writebatch_t* b,
|
||||
const size_t* keys_list_sizes, int num_values,
|
||||
const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr instead of vector to avoid overhead
|
||||
// Safe because WriteBatch::Put immediately copies the data
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep.Put(column_family->rep, SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
b->rep.Put(column_family->rep, SliceParts(key_slices.get(), num_keys),
|
||||
SliceParts(value_slices.get(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_merge(rocksdb_writebatch_t* b, const char* key,
|
||||
@@ -2225,16 +2312,18 @@ void rocksdb_writebatch_mergev(rocksdb_writebatch_t* b, int num_keys,
|
||||
const size_t* keys_list_sizes, int num_values,
|
||||
const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr instead of vector to avoid overhead
|
||||
// Safe because WriteBatch::Merge immediately copies the data
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep.Merge(SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
b->rep.Merge(SliceParts(key_slices.get(), num_keys),
|
||||
SliceParts(value_slices.get(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_mergev_cf(rocksdb_writebatch_t* b,
|
||||
@@ -2243,16 +2332,18 @@ void rocksdb_writebatch_mergev_cf(rocksdb_writebatch_t* b,
|
||||
const size_t* keys_list_sizes, int num_values,
|
||||
const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr instead of vector to avoid overhead
|
||||
// Safe because WriteBatch::Merge immediately copies the data
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep.Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
b->rep.Merge(column_family->rep, SliceParts(key_slices.get(), num_keys),
|
||||
SliceParts(value_slices.get(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_delete(rocksdb_writebatch_t* b, const char* key,
|
||||
@@ -2292,21 +2383,25 @@ void rocksdb_writebatch_singledelete_cf_with_ts(
|
||||
void rocksdb_writebatch_deletev(rocksdb_writebatch_t* b, int num_keys,
|
||||
const char* const* keys_list,
|
||||
const size_t* keys_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr instead of vector to avoid overhead
|
||||
// Safe because WriteBatch::Delete immediately copies the data
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
b->rep.Delete(SliceParts(key_slices.data(), num_keys));
|
||||
b->rep.Delete(SliceParts(key_slices.get(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_deletev_cf(
|
||||
rocksdb_writebatch_t* b, rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr instead of vector to avoid overhead
|
||||
// Safe because WriteBatch::Delete immediately copies the data
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
b->rep.Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
|
||||
b->rep.Delete(column_family->rep, SliceParts(key_slices.get(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_delete_range(rocksdb_writebatch_t* b,
|
||||
@@ -2330,14 +2425,16 @@ void rocksdb_writebatch_delete_rangev(rocksdb_writebatch_t* b, int num_keys,
|
||||
const size_t* start_keys_list_sizes,
|
||||
const char* const* end_keys_list,
|
||||
const size_t* end_keys_list_sizes) {
|
||||
std::vector<Slice> start_key_slices(num_keys);
|
||||
std::vector<Slice> end_key_slices(num_keys);
|
||||
// Use unique_ptr instead of vector to avoid overhead
|
||||
// Safe because WriteBatch::DeleteRange immediately copies the data
|
||||
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
|
||||
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
|
||||
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
|
||||
}
|
||||
b->rep.DeleteRange(SliceParts(start_key_slices.data(), num_keys),
|
||||
SliceParts(end_key_slices.data(), num_keys));
|
||||
b->rep.DeleteRange(SliceParts(start_key_slices.get(), num_keys),
|
||||
SliceParts(end_key_slices.get(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_delete_rangev_cf(
|
||||
@@ -2345,15 +2442,17 @@ void rocksdb_writebatch_delete_rangev_cf(
|
||||
int num_keys, const char* const* start_keys_list,
|
||||
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
|
||||
const size_t* end_keys_list_sizes) {
|
||||
std::vector<Slice> start_key_slices(num_keys);
|
||||
std::vector<Slice> end_key_slices(num_keys);
|
||||
// Use unique_ptr instead of vector to avoid overhead
|
||||
// Safe because WriteBatch::DeleteRange immediately copies the data
|
||||
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
|
||||
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
|
||||
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
|
||||
}
|
||||
b->rep.DeleteRange(column_family->rep,
|
||||
SliceParts(start_key_slices.data(), num_keys),
|
||||
SliceParts(end_key_slices.data(), num_keys));
|
||||
SliceParts(start_key_slices.get(), num_keys),
|
||||
SliceParts(end_key_slices.get(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_put_log_data(rocksdb_writebatch_t* b, const char* blob,
|
||||
@@ -2514,16 +2613,17 @@ void rocksdb_writebatch_wi_putv(rocksdb_writebatch_wi_t* b, int num_keys,
|
||||
const size_t* keys_list_sizes, int num_values,
|
||||
const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr for better performance (avoids vector overhead)
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep->Put(SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
b->rep->Put(SliceParts(key_slices.get(), num_keys),
|
||||
SliceParts(value_slices.get(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_wi_putv_cf(
|
||||
@@ -2559,16 +2659,17 @@ void rocksdb_writebatch_wi_mergev(rocksdb_writebatch_wi_t* b, int num_keys,
|
||||
const size_t* keys_list_sizes, int num_values,
|
||||
const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr for better performance (avoids vector overhead)
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep->Merge(SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
b->rep->Merge(SliceParts(key_slices.get(), num_keys),
|
||||
SliceParts(value_slices.get(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_wi_mergev_cf(
|
||||
@@ -2576,16 +2677,17 @@ void rocksdb_writebatch_wi_mergev_cf(
|
||||
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
int num_values, const char* const* values_list,
|
||||
const size_t* values_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr for better performance (avoids vector overhead)
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<Slice> value_slices(num_values);
|
||||
std::unique_ptr<Slice[]> value_slices(new Slice[num_values]);
|
||||
for (int i = 0; i < num_values; i++) {
|
||||
value_slices[i] = Slice(values_list[i], values_list_sizes[i]);
|
||||
}
|
||||
b->rep->Merge(column_family->rep, SliceParts(key_slices.data(), num_keys),
|
||||
SliceParts(value_slices.data(), num_values));
|
||||
b->rep->Merge(column_family->rep, SliceParts(key_slices.get(), num_keys),
|
||||
SliceParts(value_slices.get(), num_values));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_wi_delete(rocksdb_writebatch_wi_t* b, const char* key,
|
||||
@@ -2623,11 +2725,12 @@ void rocksdb_writebatch_wi_deletev(rocksdb_writebatch_wi_t* b, int num_keys,
|
||||
void rocksdb_writebatch_wi_deletev_cf(
|
||||
rocksdb_writebatch_wi_t* b, rocksdb_column_family_handle_t* column_family,
|
||||
int num_keys, const char* const* keys_list, const size_t* keys_list_sizes) {
|
||||
std::vector<Slice> key_slices(num_keys);
|
||||
// Use unique_ptr for better performance (avoids vector overhead)
|
||||
std::unique_ptr<Slice[]> key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
key_slices[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
b->rep->Delete(column_family->rep, SliceParts(key_slices.data(), num_keys));
|
||||
b->rep->Delete(column_family->rep, SliceParts(key_slices.get(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_wi_delete_range(rocksdb_writebatch_wi_t* b,
|
||||
@@ -2653,14 +2756,15 @@ void rocksdb_writebatch_wi_delete_rangev(rocksdb_writebatch_wi_t* b,
|
||||
const size_t* start_keys_list_sizes,
|
||||
const char* const* end_keys_list,
|
||||
const size_t* end_keys_list_sizes) {
|
||||
std::vector<Slice> start_key_slices(num_keys);
|
||||
std::vector<Slice> end_key_slices(num_keys);
|
||||
// Use unique_ptr for better performance (avoids vector overhead)
|
||||
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
|
||||
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
|
||||
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
|
||||
}
|
||||
b->rep->DeleteRange(SliceParts(start_key_slices.data(), num_keys),
|
||||
SliceParts(end_key_slices.data(), num_keys));
|
||||
b->rep->DeleteRange(SliceParts(start_key_slices.get(), num_keys),
|
||||
SliceParts(end_key_slices.get(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_wi_delete_rangev_cf(
|
||||
@@ -2668,15 +2772,16 @@ void rocksdb_writebatch_wi_delete_rangev_cf(
|
||||
int num_keys, const char* const* start_keys_list,
|
||||
const size_t* start_keys_list_sizes, const char* const* end_keys_list,
|
||||
const size_t* end_keys_list_sizes) {
|
||||
std::vector<Slice> start_key_slices(num_keys);
|
||||
std::vector<Slice> end_key_slices(num_keys);
|
||||
// Use unique_ptr for better performance (avoids vector overhead)
|
||||
std::unique_ptr<Slice[]> start_key_slices(new Slice[num_keys]);
|
||||
std::unique_ptr<Slice[]> end_key_slices(new Slice[num_keys]);
|
||||
for (int i = 0; i < num_keys; i++) {
|
||||
start_key_slices[i] = Slice(start_keys_list[i], start_keys_list_sizes[i]);
|
||||
end_key_slices[i] = Slice(end_keys_list[i], end_keys_list_sizes[i]);
|
||||
}
|
||||
b->rep->DeleteRange(column_family->rep,
|
||||
SliceParts(start_key_slices.data(), num_keys),
|
||||
SliceParts(end_key_slices.data(), num_keys));
|
||||
SliceParts(start_key_slices.get(), num_keys),
|
||||
SliceParts(end_key_slices.get(), num_keys));
|
||||
}
|
||||
|
||||
void rocksdb_writebatch_wi_put_log_data(rocksdb_writebatch_wi_t* b,
|
||||
@@ -2794,12 +2899,13 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db(
|
||||
const rocksdb_readoptions_t* options, const char* key, size_t keylen,
|
||||
size_t* vallen, char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
// Use PinnableSlice to avoid unnecessary allocations
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = wbwi->rep->GetFromBatchAndDB(db->rep, options->rep,
|
||||
Slice(key, keylen), &tmp);
|
||||
Slice(key, keylen), &pinnable_val);
|
||||
if (s.ok()) {
|
||||
*vallen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vallen = pinnable_val.size();
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vallen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -2832,12 +2938,14 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
size_t keylen, size_t* vallen, char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s = wbwi->rep->GetFromBatchAndDB(
|
||||
db->rep, options->rep, column_family->rep, Slice(key, keylen), &tmp);
|
||||
// Use PinnableSlice to avoid unnecessary allocations
|
||||
PinnableSlice pinnable_val;
|
||||
Status s =
|
||||
wbwi->rep->GetFromBatchAndDB(db->rep, options->rep, column_family->rep,
|
||||
Slice(key, keylen), &pinnable_val);
|
||||
if (s.ok()) {
|
||||
*vallen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vallen = pinnable_val.size();
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vallen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -6922,11 +7030,11 @@ char* rocksdb_transaction_get(rocksdb_transaction_t* txn,
|
||||
const char* key, size_t klen, size_t* vlen,
|
||||
char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s = txn->rep->Get(options->rep, Slice(key, klen), &tmp);
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = txn->rep->Get(options->rep, Slice(key, klen), &pinnable_val);
|
||||
if (s.ok()) {
|
||||
*vlen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vlen = pinnable_val.size();
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vlen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -6957,12 +7065,12 @@ char* rocksdb_transaction_get_cf(rocksdb_transaction_t* txn,
|
||||
const char* key, size_t klen, size_t* vlen,
|
||||
char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s =
|
||||
txn->rep->Get(options->rep, column_family->rep, Slice(key, klen), &tmp);
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = txn->rep->Get(options->rep, column_family->rep, Slice(key, klen),
|
||||
&pinnable_val);
|
||||
if (s.ok()) {
|
||||
*vlen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vlen = pinnable_val.size();
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vlen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -6996,12 +7104,12 @@ char* rocksdb_transaction_get_for_update(rocksdb_transaction_t* txn,
|
||||
size_t* vlen, unsigned char exclusive,
|
||||
char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s =
|
||||
txn->rep->GetForUpdate(options->rep, Slice(key, klen), &tmp, exclusive);
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = txn->rep->GetForUpdate(options->rep, Slice(key, klen),
|
||||
&pinnable_val, exclusive);
|
||||
if (s.ok()) {
|
||||
*vlen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vlen = pinnable_val.size();
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vlen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -7033,12 +7141,12 @@ char* rocksdb_transaction_get_for_update_cf(
|
||||
rocksdb_column_family_handle_t* column_family, const char* key, size_t klen,
|
||||
size_t* vlen, unsigned char exclusive, char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = txn->rep->GetForUpdate(options->rep, column_family->rep,
|
||||
Slice(key, klen), &tmp, exclusive);
|
||||
Slice(key, klen), &pinnable_val, exclusive);
|
||||
if (s.ok()) {
|
||||
*vlen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vlen = pinnable_val.size();
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vlen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -7072,10 +7180,13 @@ void rocksdb_transaction_multi_get(rocksdb_transaction_t* txn,
|
||||
const size_t* keys_list_sizes,
|
||||
char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
// Use unique_ptr for efficiency
|
||||
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
// Note: Transaction only has vector-based MultiGet API
|
||||
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
txn->rep->MultiGet(options->rep, keys, &values);
|
||||
@@ -7101,10 +7212,14 @@ void rocksdb_transaction_multi_get_for_update(
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
// Use unique_ptr for efficiency
|
||||
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
// Note: GetForUpdate only has vector-based API, no array-based PinnableSlice
|
||||
// variant
|
||||
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
txn->rep->MultiGetForUpdate(options->rep, keys, &values);
|
||||
@@ -7131,12 +7246,15 @@ void rocksdb_transaction_multi_get_cf(
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
// Use unique_ptr for efficiency
|
||||
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
|
||||
std::vector<ColumnFamilyHandle*> cfs(num_keys);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
cfs[i] = column_families[i]->rep;
|
||||
}
|
||||
// Note: Transaction only has vector-based MultiGet API
|
||||
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
txn->rep->MultiGet(options->rep, cfs, keys, &values);
|
||||
@@ -7163,12 +7281,16 @@ void rocksdb_transaction_multi_get_for_update_cf(
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
// Use unique_ptr for efficiency
|
||||
std::unique_ptr<Slice[]> keys_arr(new Slice[num_keys]);
|
||||
std::vector<ColumnFamilyHandle*> cfs(num_keys);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
keys_arr[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
cfs[i] = column_families[i]->rep;
|
||||
}
|
||||
// Note: GetForUpdate only has vector-based API, no array-based PinnableSlice
|
||||
// variant
|
||||
std::vector<Slice> keys(keys_arr.get(), keys_arr.get() + num_keys);
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
txn->rep->MultiGetForUpdate(options->rep, cfs, keys, &values);
|
||||
@@ -7195,11 +7317,12 @@ char* rocksdb_transactiondb_get(rocksdb_transactiondb_t* txn_db,
|
||||
const char* key, size_t klen, size_t* vlen,
|
||||
char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
Status s = txn_db->rep->Get(options->rep, Slice(key, klen), &tmp);
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = txn_db->rep->Get(options->rep, txn_db->rep->DefaultColumnFamily(),
|
||||
Slice(key, klen), &pinnable_val);
|
||||
if (s.ok()) {
|
||||
*vlen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vlen = pinnable_val.size();
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vlen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -7230,12 +7353,12 @@ char* rocksdb_transactiondb_get_cf(
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
size_t keylen, size_t* vallen, char** errptr) {
|
||||
char* result = nullptr;
|
||||
std::string tmp;
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = txn_db->rep->Get(options->rep, column_family->rep,
|
||||
Slice(key, keylen), &tmp);
|
||||
Slice(key, keylen), &pinnable_val);
|
||||
if (s.ok()) {
|
||||
*vallen = tmp.size();
|
||||
result = CopyString(tmp);
|
||||
*vallen = pinnable_val.size();
|
||||
result = CopyString(pinnable_val);
|
||||
} else {
|
||||
*vallen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
@@ -7269,13 +7392,17 @@ void rocksdb_transactiondb_multi_get(rocksdb_transactiondb_t* txn_db,
|
||||
const size_t* keys_list_sizes,
|
||||
char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
// Use unique_ptr for efficiency
|
||||
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
txn_db->rep->MultiGet(options->rep, keys, &values);
|
||||
// Use PinnableSlice to avoid unnecessary allocations
|
||||
auto cfh = txn_db->rep->DefaultColumnFamily();
|
||||
std::vector<PinnableSlice> values(num_keys);
|
||||
std::vector<Status> statuses(num_keys);
|
||||
txn_db->rep->MultiGet(options->rep, cfh, num_keys, keys.get(), values.data(),
|
||||
statuses.data());
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
@@ -7299,15 +7426,18 @@ void rocksdb_transactiondb_multi_get_cf(
|
||||
size_t num_keys, const char* const* keys_list,
|
||||
const size_t* keys_list_sizes, char** values_list,
|
||||
size_t* values_list_sizes, char** errs) {
|
||||
std::vector<Slice> keys(num_keys);
|
||||
std::vector<ColumnFamilyHandle*> cfs(num_keys);
|
||||
// Use unique_ptr for efficiency
|
||||
std::unique_ptr<Slice[]> keys(new Slice[num_keys]);
|
||||
std::unique_ptr<ColumnFamilyHandle*[]> cfs(new ColumnFamilyHandle*[num_keys]);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
keys[i] = Slice(keys_list[i], keys_list_sizes[i]);
|
||||
cfs[i] = column_families[i]->rep;
|
||||
}
|
||||
std::vector<std::string> values(num_keys);
|
||||
std::vector<Status> statuses =
|
||||
txn_db->rep->MultiGet(options->rep, cfs, keys, &values);
|
||||
// Use PinnableSlice to avoid unnecessary allocations
|
||||
std::vector<PinnableSlice> values(num_keys);
|
||||
std::vector<Status> statuses(num_keys);
|
||||
txn_db->rep->MultiGet(options->rep, num_keys, cfs.get(), keys.get(),
|
||||
values.data(), statuses.data());
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
if (statuses[i].ok()) {
|
||||
values_list[i] = CopyString(values[i]);
|
||||
@@ -7900,4 +8030,110 @@ uint64_t rocksdb_wait_for_compact_options_get_timeout(
|
||||
return opt->rep.timeout.count();
|
||||
}
|
||||
|
||||
/* High-performance zero-copy Get implementations */
|
||||
|
||||
struct rocksdb_pinnable_handle_t {
|
||||
PinnableSlice rep;
|
||||
};
|
||||
|
||||
rocksdb_pinnable_handle_t* rocksdb_get_pinned_v2(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
|
||||
size_t keylen, char** errptr) {
|
||||
rocksdb_pinnable_handle_t* handle = new rocksdb_pinnable_handle_t;
|
||||
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
|
||||
Slice(key, keylen), &handle->rep);
|
||||
if (!s.ok()) {
|
||||
delete handle;
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
rocksdb_pinnable_handle_t* rocksdb_get_pinned_cf_v2(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
size_t keylen, char** errptr) {
|
||||
rocksdb_pinnable_handle_t* handle = new rocksdb_pinnable_handle_t;
|
||||
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
|
||||
&handle->rep);
|
||||
if (!s.ok()) {
|
||||
delete handle;
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
const char* rocksdb_pinnable_handle_get_value(
|
||||
const rocksdb_pinnable_handle_t* handle, size_t* vallen) {
|
||||
if (!handle) {
|
||||
*vallen = 0;
|
||||
return nullptr;
|
||||
}
|
||||
*vallen = handle->rep.size();
|
||||
return handle->rep.data();
|
||||
}
|
||||
|
||||
void rocksdb_pinnable_handle_destroy(rocksdb_pinnable_handle_t* handle) {
|
||||
delete handle;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_get_into_buffer(rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, size_t keylen,
|
||||
char* buffer, size_t buffer_size,
|
||||
size_t* vallen, unsigned char* found,
|
||||
char** errptr) {
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = db->rep->Get(options->rep, db->rep->DefaultColumnFamily(),
|
||||
Slice(key, keylen), &pinnable_val);
|
||||
if (s.ok()) {
|
||||
*found = 1;
|
||||
*vallen = pinnable_val.size();
|
||||
if (buffer_size >= pinnable_val.size()) {
|
||||
memcpy(buffer, pinnable_val.data(), pinnable_val.size());
|
||||
return 1; // Success - data copied
|
||||
}
|
||||
return 0; // Buffer too small
|
||||
} else {
|
||||
*found = 0;
|
||||
*vallen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char rocksdb_get_into_buffer_cf(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
size_t keylen, char* buffer, size_t buffer_size, size_t* vallen,
|
||||
unsigned char* found, char** errptr) {
|
||||
PinnableSlice pinnable_val;
|
||||
Status s = db->rep->Get(options->rep, column_family->rep, Slice(key, keylen),
|
||||
&pinnable_val);
|
||||
if (s.ok()) {
|
||||
*found = 1;
|
||||
*vallen = pinnable_val.size();
|
||||
if (buffer_size >= pinnable_val.size()) {
|
||||
memcpy(buffer, pinnable_val.data(), pinnable_val.size());
|
||||
return 1; // Success - data copied
|
||||
}
|
||||
return 0; // Buffer too small
|
||||
} else {
|
||||
*found = 0;
|
||||
*vallen = 0;
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // end extern "C"
|
||||
+276
@@ -1255,6 +1255,70 @@ int main(int argc, char** argv) {
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
}
|
||||
|
||||
StartPhase("writebatch_vectors_cf");
|
||||
{
|
||||
const char* cf_name = "wb_vectors_cf";
|
||||
rocksdb_column_family_handle_t* wb_cf =
|
||||
rocksdb_create_column_family(db, options, cf_name, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
|
||||
|
||||
// Test putv_cf: concatenates multiple slices into a single key/value
|
||||
const char* put_keys[2] = {"k", "ey"};
|
||||
const size_t put_key_sizes[2] = {1, 2};
|
||||
const char* put_vals[3] = {"v", "a", "l"};
|
||||
const size_t put_val_sizes[3] = {1, 1, 1};
|
||||
rocksdb_writebatch_putv_cf(wb, wb_cf, 2, put_keys, put_key_sizes, 3,
|
||||
put_vals, put_val_sizes);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
// putv_cf concatenates: key="k"+"ey"="key", value="v"+"a"+"l"="val"
|
||||
CheckGetCF(db, roptions, wb_cf, "key", "val");
|
||||
CheckGetCF(db, roptions, wb_cf, "k", NULL);
|
||||
CheckGetCF(db, roptions, wb_cf, "ey", NULL);
|
||||
|
||||
// Test deletev_cf: concatenates multiple slices for key
|
||||
rocksdb_writebatch_clear(wb);
|
||||
const char* del_keys[2] = {"k", "ey"};
|
||||
const size_t del_key_sizes[2] = {1, 2};
|
||||
rocksdb_writebatch_deletev_cf(wb, wb_cf, 2, del_keys, del_key_sizes);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
CheckGetCF(db, roptions, wb_cf, "key", NULL);
|
||||
|
||||
// Test delete_rangev_cf: concatenates slices for range deletion
|
||||
rocksdb_writebatch_clear(wb);
|
||||
rocksdb_writebatch_put_cf(wb, wb_cf, "a", 1, "1", 1);
|
||||
rocksdb_writebatch_put_cf(wb, wb_cf, "b", 1, "2", 1);
|
||||
rocksdb_writebatch_put_cf(wb, wb_cf, "c", 1, "3", 1);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
CheckGetCF(db, roptions, wb_cf, "a", "1");
|
||||
CheckGetCF(db, roptions, wb_cf, "b", "2");
|
||||
CheckGetCF(db, roptions, wb_cf, "c", "3");
|
||||
|
||||
rocksdb_writebatch_clear(wb);
|
||||
const char* range_start[2] = {"a", ""}; // "a" + "" = "a"
|
||||
const size_t range_start_sizes[2] = {1, 0};
|
||||
const char* range_end[2] = {"c", ""};
|
||||
const size_t range_end_sizes[2] = {1, 0};
|
||||
rocksdb_writebatch_delete_rangev_cf(wb, wb_cf, 2, range_start,
|
||||
range_start_sizes, range_end,
|
||||
range_end_sizes);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
// Range [a, c) should delete "a" and "b", but not "c"
|
||||
CheckGetCF(db, roptions, wb_cf, "a", NULL);
|
||||
CheckGetCF(db, roptions, wb_cf, "b", NULL);
|
||||
CheckGetCF(db, roptions, wb_cf, "c", "3");
|
||||
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
rocksdb_drop_column_family(db, wb_cf, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_column_family_handle_destroy(wb_cf);
|
||||
}
|
||||
|
||||
StartPhase("writebatch_vectors");
|
||||
{
|
||||
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
|
||||
@@ -1420,6 +1484,43 @@ int main(int argc, char** argv) {
|
||||
rocksdb_iter_destroy(iter);
|
||||
}
|
||||
|
||||
StartPhase("iter_slice");
|
||||
{
|
||||
// Test the new slice-based iterator API for better performance
|
||||
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
|
||||
// Test rocksdb_iter_key_slice
|
||||
rocksdb_slice_t key_slice = rocksdb_iter_key_slice(iter);
|
||||
CheckEqual("box", key_slice.data, key_slice.size);
|
||||
|
||||
// Test rocksdb_iter_value_slice
|
||||
rocksdb_slice_t value_slice = rocksdb_iter_value_slice(iter);
|
||||
CheckEqual("c", value_slice.data, value_slice.size);
|
||||
|
||||
// Move to next entry and test again
|
||||
rocksdb_iter_next(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
key_slice = rocksdb_iter_key_slice(iter);
|
||||
value_slice = rocksdb_iter_value_slice(iter);
|
||||
CheckEqual("foo", key_slice.data, key_slice.size);
|
||||
CheckEqual("hello", value_slice.data, value_slice.size);
|
||||
|
||||
// Test seeking with slice API
|
||||
rocksdb_iter_seek(iter, "b", 1);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
key_slice = rocksdb_iter_key_slice(iter);
|
||||
value_slice = rocksdb_iter_value_slice(iter);
|
||||
CheckEqual("box", key_slice.data, key_slice.size);
|
||||
CheckEqual("c", value_slice.data, value_slice.size);
|
||||
|
||||
rocksdb_iter_get_error(iter, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
}
|
||||
|
||||
StartPhase("wbwi_iter");
|
||||
{
|
||||
rocksdb_iterator_t* base_iter = rocksdb_create_iterator(db, roptions);
|
||||
@@ -1505,6 +1606,53 @@ int main(int argc, char** argv) {
|
||||
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
StartPhase("zero_copy_get_pinned_v2");
|
||||
{
|
||||
// Test new zero-copy get functions
|
||||
|
||||
// Test rocksdb_get_pinned_v2
|
||||
rocksdb_pinnable_handle_t* handle =
|
||||
rocksdb_get_pinned_v2(db, roptions, "foo", 3, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(handle != NULL);
|
||||
size_t val_len;
|
||||
const char* val = rocksdb_pinnable_handle_get_value(handle, &val_len);
|
||||
CheckEqual("hello", val, val_len);
|
||||
rocksdb_pinnable_handle_destroy(handle);
|
||||
|
||||
// Test with non-existent key
|
||||
handle = rocksdb_get_pinned_v2(db, roptions, "notfound", 8, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(handle == NULL);
|
||||
|
||||
// Test rocksdb_get_into_buffer
|
||||
char buffer[100];
|
||||
unsigned char found;
|
||||
unsigned char success = rocksdb_get_into_buffer(
|
||||
db, roptions, "foo", 3, buffer, sizeof(buffer), &val_len, &found, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(success == 1);
|
||||
CheckCondition(found == 1);
|
||||
CheckCondition(val_len == 5);
|
||||
CheckCondition(memcmp(buffer, "hello", 5) == 0);
|
||||
|
||||
// Test with buffer too small
|
||||
success = rocksdb_get_into_buffer(db, roptions, "foo", 3, buffer,
|
||||
2, // Buffer too small
|
||||
&val_len, &found, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(success == 0); // Should fail due to small buffer
|
||||
CheckCondition(found == 1);
|
||||
CheckCondition(val_len == 5); // Should still report actual size
|
||||
|
||||
// Test with non-existent key
|
||||
success = rocksdb_get_into_buffer(db, roptions, "notfound", 8, buffer,
|
||||
sizeof(buffer), &val_len, &found, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(success == 0);
|
||||
CheckCondition(found == 0);
|
||||
}
|
||||
|
||||
StartPhase("pin_get");
|
||||
{
|
||||
CheckPinGet(db, roptions, "box", "c");
|
||||
@@ -1922,6 +2070,55 @@ int main(int argc, char** argv) {
|
||||
rocksdb_flush_wal(db, 1, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// Test column family handle get name
|
||||
{
|
||||
size_t name_len;
|
||||
char* cf_name =
|
||||
rocksdb_column_family_handle_get_name(handles[1], &name_len);
|
||||
CheckCondition(name_len == 3);
|
||||
CheckCondition(memcmp(cf_name, "cf1", 3) == 0);
|
||||
rocksdb_free(cf_name);
|
||||
}
|
||||
|
||||
// Test zero-copy get with column families
|
||||
{
|
||||
rocksdb_pinnable_handle_t* handle =
|
||||
rocksdb_get_pinned_cf_v2(db, roptions, handles[1], "box", 3, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(handle != NULL);
|
||||
size_t val_len;
|
||||
const char* val = rocksdb_pinnable_handle_get_value(handle, &val_len);
|
||||
CheckEqual("c", val, val_len);
|
||||
rocksdb_pinnable_handle_destroy(handle);
|
||||
|
||||
// Test with non-existent key
|
||||
handle = rocksdb_get_pinned_cf_v2(db, roptions, handles[1], "notfound", 8,
|
||||
&err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(handle == NULL);
|
||||
|
||||
// Test rocksdb_get_into_buffer_cf
|
||||
char buffer[100];
|
||||
unsigned char found;
|
||||
unsigned char success = rocksdb_get_into_buffer_cf(
|
||||
db, roptions, handles[1], "buff", 4, buffer, sizeof(buffer), &val_len,
|
||||
&found, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(success == 1);
|
||||
CheckCondition(found == 1);
|
||||
CheckCondition(val_len == 7);
|
||||
CheckCondition(memcmp(buffer, "rocksdb", 7) == 0);
|
||||
|
||||
// Test with buffer too small
|
||||
success = rocksdb_get_into_buffer_cf(db, roptions, handles[1], "buff", 4,
|
||||
buffer, 3, // Buffer too small
|
||||
&val_len, &found, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(success == 0); // Should fail due to small buffer
|
||||
CheckCondition(found == 1);
|
||||
CheckCondition(val_len == 7); // Should still report actual size
|
||||
}
|
||||
|
||||
// Test WriteBatchWithIndex iteration with Column Family
|
||||
rocksdb_writebatch_wi_t* wbwi = rocksdb_writebatch_wi_create(0, true);
|
||||
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "boat", 4, "row",
|
||||
@@ -1998,6 +2195,74 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Test rocksdb_batched_multi_get_cf_slice for better performance
|
||||
// Build rocksdb_slice_t array directly to avoid conversion overhead
|
||||
rocksdb_slice_t batched_key_slices[4];
|
||||
batched_key_slices[0].data = "box";
|
||||
batched_key_slices[0].size = 3;
|
||||
batched_key_slices[1].data = "buff";
|
||||
batched_key_slices[1].size = 4;
|
||||
batched_key_slices[2].data = "barfooxx";
|
||||
batched_key_slices[2].size = 8;
|
||||
batched_key_slices[3].data = "box";
|
||||
batched_key_slices[3].size = 3;
|
||||
|
||||
const char* expected_value[4] = {"c", "rocksdb", NULL, "c"};
|
||||
char* batched_errs[4];
|
||||
rocksdb_pinnableslice_t* pvals[4];
|
||||
|
||||
rocksdb_batched_multi_get_cf_slice(db, roptions, handles[1], 4,
|
||||
batched_key_slices, pvals,
|
||||
batched_errs, false);
|
||||
|
||||
const char* val;
|
||||
size_t val_len;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
CheckNoError(batched_errs[i]);
|
||||
if (pvals[i] != NULL) {
|
||||
val = rocksdb_pinnableslice_value(pvals[i], &val_len);
|
||||
CheckEqual(expected_value[i], val, val_len);
|
||||
rocksdb_pinnableslice_destroy(pvals[i]);
|
||||
} else {
|
||||
CheckEqual(expected_value[i], NULL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Test rocksdb_batched_multi_get_cf_slice with sorted_input=true
|
||||
// Keys must be in sorted order for this optimization
|
||||
rocksdb_slice_t sorted_key_slices[3];
|
||||
sorted_key_slices[0].data = "box";
|
||||
sorted_key_slices[0].size = 3;
|
||||
sorted_key_slices[1].data = "buff";
|
||||
sorted_key_slices[1].size = 4;
|
||||
sorted_key_slices[2].data = "notfound";
|
||||
sorted_key_slices[2].size = 8;
|
||||
|
||||
const char* expected_value[3] = {"c", "rocksdb", NULL};
|
||||
char* batched_errs[3];
|
||||
rocksdb_pinnableslice_t* pvals[3];
|
||||
|
||||
rocksdb_batched_multi_get_cf_slice(db, roptions, handles[1], 3,
|
||||
sorted_key_slices, pvals, batched_errs,
|
||||
true);
|
||||
|
||||
const char* val;
|
||||
size_t val_len;
|
||||
for (i = 0; i < 3; ++i) {
|
||||
CheckNoError(batched_errs[i]);
|
||||
if (pvals[i] != NULL) {
|
||||
val = rocksdb_pinnableslice_value(pvals[i], &val_len);
|
||||
CheckEqual(expected_value[i], val, val_len);
|
||||
rocksdb_pinnableslice_destroy(pvals[i]);
|
||||
} else {
|
||||
CheckEqual(expected_value[i], NULL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
unsigned char value_found = 0;
|
||||
|
||||
@@ -3469,6 +3734,17 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transaction_put(txn, "foo", 3, "hello", 5, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// test transaction get/set name (before commit)
|
||||
{
|
||||
rocksdb_transaction_set_name(txn, "test_txn", 8, &err);
|
||||
CheckNoError(err);
|
||||
size_t name_len;
|
||||
char* txn_name = rocksdb_transaction_get_name(txn, &name_len);
|
||||
CheckCondition(name_len == 8);
|
||||
CheckCondition(memcmp(txn_name, "test_txn", 8) == 0);
|
||||
rocksdb_free(txn_name);
|
||||
}
|
||||
|
||||
// read from outside transaction, before commit
|
||||
CheckTxnDBGet(txn_db, roptions, "foo", NULL);
|
||||
CheckTxnDBPinGet(txn_db, roptions, "foo", NULL);
|
||||
|
||||
@@ -534,6 +534,232 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
// Helper function to generate zero-padded keys
|
||||
// e.g., MakeKey("a", 5) -> "a05", MakeKey("b", 42) -> "b42"
|
||||
static std::string MakeKey(const std::string& prefix, int index) {
|
||||
return prefix + (index < 10 ? "0" : "") + std::to_string(index);
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, TrivialMoveNonOverlappingFiles) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
|
||||
DB* db = nullptr;
|
||||
ASSERT_OK(DestroyDB(db_name_, options));
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(db, nullptr);
|
||||
|
||||
// Create 3 non-overlapping files in L0
|
||||
// File 1: keys [a00-a99]
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("a", i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// File 2: keys [b00-b99]
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("b", i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// File 3: keys [c00-c99]
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("c", i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Verify files are in L0
|
||||
ColumnFamilyMetaData meta;
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[0].files.size(), 3);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 0);
|
||||
|
||||
// Get L0 files
|
||||
std::vector<std::string> l0_files;
|
||||
for (const auto& file : meta.levels[0].files) {
|
||||
l0_files.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
|
||||
CompactionOptions compact_option;
|
||||
compact_option.allow_trivial_move = true;
|
||||
// Compact all L0 files to L1 (non-overlapping in L1)
|
||||
ASSERT_OK(db->CompactFiles(compact_option, l0_files, 1));
|
||||
|
||||
// Verify files are now in L1
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[0].files.size(), 0);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 3);
|
||||
|
||||
// Get the first file from L1 (should be the one with keys a00-a99)
|
||||
std::string l1_file_to_move;
|
||||
std::vector<std::string> l1_files_to_move_later;
|
||||
uint64_t l1_file_number = 0;
|
||||
for (const auto& file : meta.levels[1].files) {
|
||||
if (file.smallestkey[0] == 'a') {
|
||||
l1_file_to_move = file.db_path + "/" + file.name;
|
||||
l1_file_number = file.file_number;
|
||||
} else {
|
||||
l1_files_to_move_later.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
}
|
||||
ASSERT_FALSE(l1_file_to_move.empty());
|
||||
|
||||
// Set up sync point to verify trivial move path is taken
|
||||
bool trivial_move_executed = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::CompactFilesImpl:TrivialMove",
|
||||
[&](void* /*arg*/) { trivial_move_executed = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Move the file from L1 to L6 - this should be a trivial move
|
||||
// because the file doesn't overlap with anything in L6
|
||||
std::vector<std::string> files_to_move = {l1_file_to_move};
|
||||
ASSERT_OK(db->CompactFiles(compact_option, files_to_move, 6));
|
||||
|
||||
// Verify trivial move was executed
|
||||
ASSERT_TRUE(trivial_move_executed);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// Verify the file is now in L6
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 2); // Two files remain in L1
|
||||
ASSERT_EQ(meta.levels[6].files.size(), 1); // One file in L6
|
||||
|
||||
// Verify it's the correct file in L6
|
||||
bool found_file_in_l6 = false;
|
||||
for (const auto& file : meta.levels[6].files) {
|
||||
if (file.file_number == l1_file_number) {
|
||||
found_file_in_l6 = true;
|
||||
// Verify key range hasn't changed
|
||||
ASSERT_EQ(file.smallestkey[0], 'a');
|
||||
ASSERT_EQ(file.largestkey[0], 'a');
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_file_in_l6);
|
||||
|
||||
// Move the other 2 files from L1 to L6, with allow_trivial_move set to false.
|
||||
// This will trigger a normal compaction, so the 2 files will be compacted
|
||||
// into a single file in L6.
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l1_files_to_move_later, 6));
|
||||
|
||||
// Verify files in L6
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 0); // Zero files remain in L1
|
||||
ASSERT_EQ(meta.levels[6].files.size(), 2); // Two file in L6
|
||||
|
||||
// Verify data integrity - all keys should still be readable
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("a", i);
|
||||
std::string value;
|
||||
ASSERT_OK(db->Get(ReadOptions(), key, &value));
|
||||
ASSERT_EQ(value, "value_" + key);
|
||||
}
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, TrivialMoveBlockedByOverlap) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.num_levels = 7;
|
||||
|
||||
DB* db = nullptr;
|
||||
ASSERT_OK(DestroyDB(db_name_, options));
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(db, nullptr);
|
||||
|
||||
// Create a file in L6 with keys [m00-m99] (wide range)
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("m", i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Get L0 file
|
||||
ColumnFamilyMetaData meta;
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
std::vector<std::string> l0_files;
|
||||
for (const auto& file : meta.levels[0].files) {
|
||||
l0_files.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
|
||||
CompactionOptions compact_option;
|
||||
compact_option.allow_trivial_move = true;
|
||||
|
||||
// Move to L6
|
||||
ASSERT_OK(db->CompactFiles(compact_option, l0_files, 6));
|
||||
|
||||
// Now create a file in L1 with overlapping keys [m50-m60]
|
||||
for (int i = 50; i <= 60; i++) {
|
||||
std::string key = "m" + std::to_string(i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "updated_value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Get the L0 file
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
std::vector<std::string> l0_files_2;
|
||||
for (const auto& file : meta.levels[0].files) {
|
||||
l0_files_2.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
|
||||
// Move to L1
|
||||
ASSERT_OK(db->CompactFiles(compact_option, l0_files_2, 1));
|
||||
|
||||
// Get the L1 file
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 1);
|
||||
std::string l1_file =
|
||||
meta.levels[1].files[0].db_path + "/" + meta.levels[1].files[0].name;
|
||||
|
||||
// Set up sync point to verify full compaction path is taken
|
||||
bool trivial_move_executed = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::CompactFilesImpl:TrivialMove",
|
||||
[&](void* /*arg*/) { trivial_move_executed = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Try to move from L1 to L6 - this should NOT be a trivial move
|
||||
// because the file overlaps with the existing file in L6
|
||||
ASSERT_OK(db->CompactFiles(compact_option, {l1_file}, 6));
|
||||
|
||||
// Verify trivial move was NOT executed (full compaction happened)
|
||||
ASSERT_FALSE(trivial_move_executed);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// Verify the result - should have merged data in L6
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 0); // L1 should be empty
|
||||
// L6 should have the merged file (may be 1 file if merged, or 2 if not)
|
||||
ASSERT_GE(meta.levels[6].files.size(), 1);
|
||||
|
||||
// Verify updated values are present
|
||||
for (int i = 50; i <= 60; i++) {
|
||||
std::string key = "m" + std::to_string(i);
|
||||
std::string value;
|
||||
ASSERT_OK(db->Get(ReadOptions(), key, &value));
|
||||
ASSERT_EQ(value, "updated_value_" + key);
|
||||
}
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -180,6 +180,10 @@ class Compaction {
|
||||
const std::vector<CompactionInputFiles>* inputs() { return &inputs_; }
|
||||
|
||||
// Returns the LevelFilesBrief of the specified compaction input level.
|
||||
// Note that if the compaction includes standalone range deletion file,
|
||||
// this function returns the result after filtering out input files covered
|
||||
// by the range deletion file.
|
||||
// Use inputs() if you want to get the original input files.
|
||||
const LevelFilesBrief* input_levels(size_t compaction_input_level) const {
|
||||
return &input_levels_[compaction_input_level];
|
||||
}
|
||||
|
||||
@@ -812,20 +812,19 @@ Status CompactionJob::SyncOutputDirectories() {
|
||||
Status CompactionJob::VerifyOutputFiles() {
|
||||
Status status;
|
||||
std::vector<port::Thread> thread_pool;
|
||||
std::vector<const CompactionOutputs::Output*> files_output;
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
for (const auto& output : state.GetOutputs()) {
|
||||
files_output.emplace_back(&output);
|
||||
}
|
||||
}
|
||||
ColumnFamilyData* cfd = compact_->compaction->column_family_data();
|
||||
std::atomic<size_t> next_file_idx(0);
|
||||
auto verify_table = [&](Status& output_status) {
|
||||
while (true) {
|
||||
size_t file_idx = next_file_idx.fetch_add(1);
|
||||
if (file_idx >= files_output.size()) {
|
||||
break;
|
||||
}
|
||||
VerifyOutputFlags verify_output_flags =
|
||||
compact_->compaction->mutable_cf_options().verify_output_flags;
|
||||
|
||||
// For backward compatibility
|
||||
if (paranoid_file_checks_) {
|
||||
verify_output_flags |= VerifyOutputFlags::kVerifyIteration;
|
||||
verify_output_flags |= VerifyOutputFlags::kEnableForLocalCompaction;
|
||||
verify_output_flags |= VerifyOutputFlags::kEnableForRemoteCompaction;
|
||||
}
|
||||
|
||||
auto verify_table = [&](SubcompactionState& subcompaction_state) {
|
||||
for (const auto& output_file : subcompaction_state.GetOutputs()) {
|
||||
// Verify that the table is usable
|
||||
// We set for_compaction to false and don't
|
||||
// OptimizeForCompactionTableRead here because this is a special case
|
||||
@@ -834,13 +833,19 @@ Status CompactionJob::VerifyOutputFiles() {
|
||||
// verification as user reads since the goal is to cache it here for
|
||||
// further user reads
|
||||
ReadOptions verify_table_read_options(Env::IOActivity::kCompaction);
|
||||
verify_table_read_options.verify_checksums = true;
|
||||
verify_table_read_options.readahead_size =
|
||||
file_options_for_read_.compaction_readahead_size;
|
||||
|
||||
std::unique_ptr<TableReader> table_reader_guard;
|
||||
TableReader* table_reader_ptr = table_reader_guard.get();
|
||||
verify_table_read_options.rate_limiter_priority =
|
||||
GetRateLimiterPriority();
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
verify_table_read_options, file_options_, cfd->internal_comparator(),
|
||||
files_output[file_idx]->meta,
|
||||
output_file.meta,
|
||||
/*range_del_agg=*/nullptr, compact_->compaction->mutable_cf_options(),
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
/*table_reader_ptr=*/&table_reader_ptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
TableReaderCaller::kCompactionRefill, /*arena=*/nullptr,
|
||||
@@ -850,38 +855,63 @@ Status CompactionJob::VerifyOutputFiles() {
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false);
|
||||
auto s = iter->status();
|
||||
if (s.ok()) {
|
||||
// Check for remote/local compaction and verify_output_flags flags
|
||||
const bool should_verify =
|
||||
(subcompaction_state.compaction_job_stats.is_remote_compaction &&
|
||||
!!(verify_output_flags &
|
||||
VerifyOutputFlags::kEnableForRemoteCompaction)) ||
|
||||
(!subcompaction_state.compaction_job_stats.is_remote_compaction &&
|
||||
!!(verify_output_flags &
|
||||
VerifyOutputFlags::kEnableForLocalCompaction));
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
OutputValidator validator(cfd->internal_comparator(),
|
||||
/*_enable_hash=*/true);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
s = validator.Add(iter->key(), iter->value());
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
if (should_verify) {
|
||||
const bool should_verify_block_checksum =
|
||||
!!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
|
||||
const bool should_verify_iteration =
|
||||
!!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
|
||||
if (should_verify_block_checksum) {
|
||||
assert(table_reader_ptr != nullptr);
|
||||
// If verifying iteration as well, verify meta blocks here only to
|
||||
// avoid redundant checks on data blocks
|
||||
s = table_reader_ptr->VerifyChecksum(
|
||||
verify_table_read_options, TableReaderCaller::kCompaction,
|
||||
/*meta_blocks_only=*/should_verify_iteration);
|
||||
}
|
||||
if (s.ok() && should_verify_iteration) {
|
||||
OutputValidator validator(cfd->internal_comparator(),
|
||||
/*_enable_hash=*/true);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
s = validator.Add(iter->key(), iter->value());
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = iter->status();
|
||||
}
|
||||
if (s.ok() && !validator.CompareValidator(output_file.validator)) {
|
||||
s = Status::Corruption(
|
||||
"Key-value checksum of compaction output doesn't match what "
|
||||
"was computed when written");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
s = iter->status();
|
||||
}
|
||||
if (s.ok() &&
|
||||
!validator.CompareValidator(files_output[file_idx]->validator)) {
|
||||
s = Status::Corruption("Paranoid checksums do not match");
|
||||
}
|
||||
}
|
||||
|
||||
delete iter;
|
||||
|
||||
if (!s.ok()) {
|
||||
output_status = s;
|
||||
subcompaction_state.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));
|
||||
std::ref(compact_->sub_compact_states[i]));
|
||||
}
|
||||
verify_table(compact_->sub_compact_states[0].status);
|
||||
verify_table(compact_->sub_compact_states[0]);
|
||||
for (auto& thread : thread_pool) {
|
||||
thread.join();
|
||||
}
|
||||
@@ -1450,11 +1480,11 @@ CompactionJob::CreateFileHandlers(SubcompactionState* sub_compact,
|
||||
const CompactionFileCloseFunc close_file_func =
|
||||
[this, sub_compact, start_user_key, end_user_key](
|
||||
const Status& status,
|
||||
const ParsedInternalKey& prev_table_last_internal_key,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key,
|
||||
const Slice& next_table_min_key, const CompactionIterator* c_iter,
|
||||
CompactionOutputs& outputs) {
|
||||
return this->FinishCompactionOutputFile(
|
||||
status, prev_table_last_internal_key, next_table_min_key,
|
||||
status, prev_iter_output_internal_key, next_table_min_key,
|
||||
start_user_key, end_user_key, c_iter, sub_compact, outputs);
|
||||
};
|
||||
|
||||
@@ -1469,8 +1499,8 @@ Status CompactionJob::ProcessKeyValue(
|
||||
const uint64_t kRecordStatsEvery = 1000;
|
||||
[[maybe_unused]] const std::optional<const Slice> end = sub_compact->end;
|
||||
|
||||
IterKey last_output_key;
|
||||
ParsedInternalKey last_output_ikey;
|
||||
IterKey prev_iter_output_key;
|
||||
ParsedInternalKey prev_iter_output_internal_key;
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionJob::ProcessKeyValueCompaction()::Processing",
|
||||
@@ -1521,9 +1551,9 @@ Status CompactionJob::ProcessKeyValue(
|
||||
// and `close_file_func`.
|
||||
// TODO: it would be better to have the compaction file open/close moved
|
||||
// into `CompactionOutputs` which has the output file information.
|
||||
status =
|
||||
sub_compact->AddToOutput(*c_iter, use_proximal_output, open_file_func,
|
||||
close_file_func, last_output_ikey);
|
||||
status = sub_compact->AddToOutput(*c_iter, use_proximal_output,
|
||||
open_file_func, close_file_func,
|
||||
prev_iter_output_internal_key);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -1532,9 +1562,10 @@ Status CompactionJob::ProcessKeyValue(
|
||||
static_cast<void*>(const_cast<std::atomic<bool>*>(
|
||||
&manual_compaction_canceled_)));
|
||||
|
||||
last_output_key.SetInternalKey(c_iter->key(), &last_output_ikey);
|
||||
last_output_ikey.sequence = ikey.sequence;
|
||||
last_output_ikey.type = ikey.type;
|
||||
prev_iter_output_key.SetInternalKey(c_iter->key(),
|
||||
&prev_iter_output_internal_key);
|
||||
prev_iter_output_internal_key.sequence = ikey.sequence;
|
||||
prev_iter_output_internal_key.type = ikey.type;
|
||||
c_iter->Next();
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -1841,7 +1872,7 @@ void CompactionJob::RecordDroppedKeys(
|
||||
|
||||
Status CompactionJob::FinishCompactionOutputFile(
|
||||
const Status& input_status,
|
||||
const ParsedInternalKey& prev_table_last_internal_key,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key,
|
||||
const Slice& next_table_min_key, const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key, const CompactionIterator* c_iter,
|
||||
SubcompactionState* sub_compact, CompactionOutputs& outputs) {
|
||||
@@ -2019,7 +2050,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
|
||||
if (s.ok() && ShouldUpdateSubcompactionProgress(sub_compact, c_iter,
|
||||
prev_table_last_internal_key,
|
||||
prev_iter_output_internal_key,
|
||||
next_table_min_key, meta)) {
|
||||
UpdateSubcompactionProgress(c_iter, next_table_min_key, sub_compact);
|
||||
s = PersistSubcompactionProgress(sub_compact);
|
||||
@@ -2030,10 +2061,10 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
|
||||
bool CompactionJob::ShouldUpdateSubcompactionProgress(
|
||||
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
|
||||
const ParsedInternalKey& prev_table_last_internal_key,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key,
|
||||
const Slice& next_table_min_internal_key, const FileMetaData* meta) const {
|
||||
const auto* cfd = sub_compact->compaction->column_family_data();
|
||||
// No need to update when the output will not get persisted
|
||||
// No need to update when the progress will not get persisted
|
||||
if (compaction_progress_writer_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -2057,19 +2088,24 @@ bool CompactionJob::ShouldUpdateSubcompactionProgress(
|
||||
}
|
||||
|
||||
// LIMITATION: Compaction progress persistence disabled for file boundaries
|
||||
// contaning range deletions. Range deletions can span file boundaries, making
|
||||
// it difficult (but possible) to ensure adjacent output tables have different
|
||||
// user keys. See the last check for why different users keys of adjacent
|
||||
// output tables are needed
|
||||
// containing range deletions. Range deletions can span file boundaries,
|
||||
// making it difficult to ensure adjacent output tables have different user
|
||||
// keys. See the last check for why different users keys of adjacent output
|
||||
// tables are needed
|
||||
const ValueType next_table_min_internal_key_type =
|
||||
ExtractValueType(next_table_min_internal_key);
|
||||
const ValueType prev_table_last_internal_key_type =
|
||||
prev_table_last_internal_key.user_key.empty()
|
||||
const ValueType prev_iter_output_internal_key_type =
|
||||
prev_iter_output_internal_key.user_key.empty()
|
||||
? ValueType::kTypeValue
|
||||
: prev_table_last_internal_key.type;
|
||||
: prev_iter_output_internal_key.type;
|
||||
|
||||
if (next_table_min_internal_key_type == ValueType::kTypeRangeDeletion ||
|
||||
prev_table_last_internal_key_type == ValueType::kTypeRangeDeletion) {
|
||||
// Range deletes truncated to align with file boundaries may be output by the
|
||||
// compaction iterator with `ValueType::kTypeMaxValid` instead of the original
|
||||
// type.
|
||||
if ((next_table_min_internal_key_type == ValueType::kTypeRangeDeletion ||
|
||||
next_table_min_internal_key_type == ValueType::kTypeMaxValid) ||
|
||||
(prev_iter_output_internal_key_type == ValueType::kTypeRangeDeletion ||
|
||||
prev_iter_output_internal_key_type == ValueType::kTypeMaxValid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2079,9 +2115,9 @@ bool CompactionJob::ShouldUpdateSubcompactionProgress(
|
||||
const Slice next_table_min_user_key =
|
||||
ExtractUserKey(next_table_min_internal_key);
|
||||
const Slice prev_table_last_user_key =
|
||||
prev_table_last_internal_key.user_key.empty()
|
||||
prev_iter_output_internal_key.user_key.empty()
|
||||
? Slice()
|
||||
: prev_table_last_internal_key.user_key;
|
||||
: prev_iter_output_internal_key.user_key;
|
||||
|
||||
if (cfd->user_comparator()->EqualWithoutTimestamp(next_table_min_user_key,
|
||||
prev_table_last_user_key)) {
|
||||
|
||||
@@ -430,7 +430,7 @@ class CompactionJob {
|
||||
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status,
|
||||
const ParsedInternalKey& prev_table_last_internal_key,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key,
|
||||
const Slice& next_table_min_key, const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key, const CompactionIterator* c_iter,
|
||||
SubcompactionState* sub_compact, CompactionOutputs& outputs);
|
||||
@@ -545,7 +545,7 @@ class CompactionJob {
|
||||
|
||||
bool ShouldUpdateSubcompactionProgress(
|
||||
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
|
||||
const ParsedInternalKey& prev_table_last_internal_key,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key,
|
||||
const Slice& next_table_min_internal_key, const FileMetaData* meta) const;
|
||||
|
||||
void UpdateSubcompactionProgress(const CompactionIterator* c_iter,
|
||||
|
||||
@@ -211,12 +211,12 @@ class CompactionJobTestBase : public testing::Test {
|
||||
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_,
|
||||
dbname_, &db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false)),
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
error_handler_(nullptr, db_options_, &mutex_),
|
||||
@@ -546,13 +546,13 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ASSERT_OK(s);
|
||||
db_options_.info_log = info_log;
|
||||
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
compaction_job_stats_.Reset();
|
||||
|
||||
VersionEdit new_db;
|
||||
@@ -2420,10 +2420,11 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
|
||||
|
||||
protected:
|
||||
static constexpr const char* kCancelBeforeThisKey = "cancel_before_this_key";
|
||||
std::string progress_dir_ = "";
|
||||
std::string progress_dir_;
|
||||
bool enable_cancel_ = false;
|
||||
std::atomic<int> stop_count_{0};
|
||||
std::atomic<bool> cancel_{false};
|
||||
SequenceNumber cancel_before_seqno = kMaxSequenceNumber;
|
||||
|
||||
void SetUp() override {
|
||||
CompactionJobTestBase::SetUp();
|
||||
@@ -2437,7 +2438,9 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
|
||||
if (enable_cancel_) {
|
||||
ParsedInternalKey parsed_key;
|
||||
if (ParseInternalKey(pair->second, &parsed_key, true).ok()) {
|
||||
if (parsed_key.user_key == kCancelBeforeThisKey) {
|
||||
if (parsed_key.user_key == kCancelBeforeThisKey &&
|
||||
(cancel_before_seqno == kMaxSequenceNumber ||
|
||||
parsed_key.sequence == cancel_before_seqno)) {
|
||||
cancel_.store(true);
|
||||
}
|
||||
}
|
||||
@@ -2580,7 +2583,9 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
|
||||
|
||||
while (reader.ReadRecord(&slice, &record)) {
|
||||
VersionEdit edit;
|
||||
if (!edit.DecodeFrom(slice).ok()) continue;
|
||||
if (!edit.DecodeFrom(slice).ok()) {
|
||||
continue;
|
||||
}
|
||||
builder.ProcessVersionEdit(edit);
|
||||
}
|
||||
|
||||
@@ -2663,7 +2668,7 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
|
||||
const std::initializer_list<mock::KVPair>& input_file_2,
|
||||
uint64_t last_sequence, const std::vector<uint64_t>& snapshots,
|
||||
const std::string& expected_next_key_to_compact,
|
||||
const std::vector<std::string>& expected_input_keys, bool exists_progress,
|
||||
const std::vector<std::string>& expected_input_keys,
|
||||
bool cancelled_past_mid_point = false) {
|
||||
std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
@@ -2699,7 +2704,7 @@ class ResumableCompactionJobTest : public CompactionJobTestBase {
|
||||
|
||||
// Resume compaction
|
||||
CompactionProgress compaction_progress;
|
||||
if (exists_progress) {
|
||||
if (expected_next_key_to_compact != "") {
|
||||
compaction_progress.push_back(
|
||||
ReadAndParseProgress(compaction_progress_file));
|
||||
}
|
||||
@@ -2772,35 +2777,43 @@ TEST_F(ResumableCompactionJobTest, BasicProgressResume) {
|
||||
4U /* last_sequence */, {} /* snapshots */,
|
||||
kCancelBeforeThisKey /* expected_next_key_to_compact */,
|
||||
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
|
||||
true /* exists_progress */, true /* cancelled_past_mid_point*/);
|
||||
true /* cancelled_past_mid_point */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSameKey) {
|
||||
NewDB();
|
||||
|
||||
// `cancel_before_seqno` is set to 0U to force cancellation after
|
||||
// `kCancelBeforeThisKey@1` instead of `kCancelBeforeThisKey@2`.
|
||||
// The seqno is 0 because `kCancelBeforeThisKey@1` will have its sequence
|
||||
// number zeroed during compaction while `kCancelBeforeThisKey@2` won't be
|
||||
cancel_before_seqno = 0U;
|
||||
RunCancelAndResumeTest(
|
||||
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
|
||||
"val1"}} /* input_file_1 */,
|
||||
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeValue),
|
||||
"val2"}} /* input_file_2 */,
|
||||
2U /* last_sequence */, {1U} /* snapshots */,
|
||||
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeValue), "val11"},
|
||||
{KeyStr("d", 3U, kTypeValue), "val2"}} /* input_file_2 */,
|
||||
3U /* last_sequence */, {1U} /* snapshots */,
|
||||
"" /* expected_next_key_to_compact */,
|
||||
{kCancelBeforeThisKey, kCancelBeforeThisKey} /* expected_input_keys */,
|
||||
false /* exists_progress */);
|
||||
{kCancelBeforeThisKey, kCancelBeforeThisKey,
|
||||
"d"} /* expected_input_keys */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeleteRange) {
|
||||
NewDB();
|
||||
|
||||
RunCancelAndResumeTest(
|
||||
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
|
||||
"val1"}} /* input_file_1 */,
|
||||
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeRangeDeletion),
|
||||
"val2"}} /* input_file_2 */,
|
||||
2U /* last_sequence */, {1U} /* snapshots */,
|
||||
"" /* expected_next_key_to_compact */,
|
||||
{kCancelBeforeThisKey, kCancelBeforeThisKey} /* expected_input_keys */,
|
||||
false /* exists_progress */);
|
||||
{{KeyStr("a", 1U, kTypeValue), "val1"},
|
||||
{KeyStr("b", 2U, kTypeValue), "val2"},
|
||||
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
|
||||
"val3"}} /* input_file_1 */,
|
||||
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeRangeDeletion),
|
||||
"range_deletion_end_key"},
|
||||
{KeyStr("d", 5U, kTypeValue), "val4"}} /* input_file_2 */,
|
||||
5U /* last_sequence */, {3U} /* snapshots */,
|
||||
"b" /* expected_next_key_to_compact */,
|
||||
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
|
||||
"d"} /* expected_input_keys */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnMerge) {
|
||||
@@ -2815,8 +2828,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnMerge) {
|
||||
"val4"}} /* input_file_2 */,
|
||||
4U /* last_sequence */, {} /* snapshots */,
|
||||
"bb" /* expected_next_key_to_compact */,
|
||||
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
|
||||
true /* exists_progress */);
|
||||
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
|
||||
@@ -2832,8 +2844,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
|
||||
5U /* last_sequence */, {3U} /* snapshots */,
|
||||
"b" /* expected_next_key_to_compact */,
|
||||
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
|
||||
"d"} /* expected_input_keys */,
|
||||
true /* exists_progress */);
|
||||
"d"} /* expected_input_keys */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeletionAtBottom) {
|
||||
@@ -2849,8 +2860,7 @@ TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeletionAtBottom) {
|
||||
5U /* last_sequence */, {3U} /* snapshots */,
|
||||
"b" /* expected_next_key_to_compact */,
|
||||
{"a", "b", kCancelBeforeThisKey, kCancelBeforeThisKey,
|
||||
"d"} /* expected_input_keys */,
|
||||
true /* exists_progress */);
|
||||
"d"} /* expected_input_keys */);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -278,7 +278,11 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
}
|
||||
|
||||
// reach the max file size
|
||||
if (current_output_file_size_ >= compaction_->max_output_file_size()) {
|
||||
uint64_t estimated_file_size = current_output_file_size_;
|
||||
if (compaction_->mutable_cf_options().target_file_size_is_upper_bound) {
|
||||
estimated_file_size += builder_->EstimatedTailSize();
|
||||
}
|
||||
if (estimated_file_size >= compaction_->max_output_file_size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -360,7 +364,7 @@ Status CompactionOutputs::AddToOutput(
|
||||
const CompactionIterator& c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_table_last_internal_key) {
|
||||
const ParsedInternalKey& prev_iter_output_internal_key) {
|
||||
Status s;
|
||||
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
|
||||
if (is_range_del && compaction_->bottommost_level()) {
|
||||
@@ -371,8 +375,8 @@ Status CompactionOutputs::AddToOutput(
|
||||
}
|
||||
const Slice& key = c_iter.key();
|
||||
if (ShouldStopBefore(c_iter) && HasBuilder()) {
|
||||
s = close_file_func(c_iter.InputStatus(), prev_table_last_internal_key, key,
|
||||
&c_iter, *this);
|
||||
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
|
||||
key, &c_iter, *this);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ class CompactionOutputs {
|
||||
Status AddToOutput(const CompactionIterator& c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_table_last_internal_key);
|
||||
const ParsedInternalKey& prev_iter_output_internal_key);
|
||||
|
||||
// Close the current output. `open_file_func` is needed for creating new file
|
||||
// for range-dels only output file.
|
||||
|
||||
@@ -242,7 +242,7 @@ bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
|
||||
GetRange(*inputs, &smallest, &largest);
|
||||
inputs->clear();
|
||||
vstorage->GetOverlappingInputs(level, &smallest, &largest, &inputs->files,
|
||||
hint_index, &hint_index, true,
|
||||
hint_index, &hint_index, true, nullptr,
|
||||
next_smallest);
|
||||
} while (inputs->size() > old_size);
|
||||
|
||||
@@ -465,7 +465,8 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, CompactionInputFiles* inputs,
|
||||
CompactionInputFiles* output_level_inputs, int* parent_index,
|
||||
int base_index, bool only_expand_towards_right) {
|
||||
int base_index, bool only_expand_towards_right,
|
||||
const FileMetaData* starting_l0_file) {
|
||||
assert(!inputs->empty());
|
||||
assert(output_level_inputs->empty());
|
||||
const int input_level = inputs->level;
|
||||
@@ -521,11 +522,11 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
// Round-robin compaction only allows expansion towards the larger side.
|
||||
vstorage->GetOverlappingInputs(input_level, &smallest, &all_limit,
|
||||
&expanded_inputs.files, base_index,
|
||||
nullptr);
|
||||
nullptr, true, starting_l0_file);
|
||||
} else {
|
||||
vstorage->GetOverlappingInputs(input_level, &all_start, &all_limit,
|
||||
&expanded_inputs.files, base_index,
|
||||
nullptr);
|
||||
nullptr, true, starting_l0_file);
|
||||
}
|
||||
uint64_t expanded_inputs_size = TotalFileSize(expanded_inputs.files);
|
||||
if (!ExpandInputsToCleanCut(cf_name, vstorage, &expanded_inputs)) {
|
||||
@@ -1231,7 +1232,7 @@ void CompactionPicker::PickFilesMarkedForCompaction(
|
||||
|
||||
bool CompactionPicker::GetOverlappingL0Files(
|
||||
VersionStorageInfo* vstorage, CompactionInputFiles* start_level_inputs,
|
||||
int output_level, int* parent_index) {
|
||||
int output_level, int* parent_index, const FileMetaData* starting_l0_file) {
|
||||
// Two level 0 compaction won't run at the same time, so don't need to worry
|
||||
// about files on level 0 being compacted.
|
||||
assert(level0_compactions_in_progress()->empty());
|
||||
@@ -1242,7 +1243,11 @@ bool CompactionPicker::GetOverlappingL0Files(
|
||||
// which will include the picked file.
|
||||
start_level_inputs->files.clear();
|
||||
vstorage->GetOverlappingInputs(0, &smallest, &largest,
|
||||
&(start_level_inputs->files));
|
||||
&(start_level_inputs->files),
|
||||
/*hint_index=*/-1,
|
||||
/*file_index=*/nullptr,
|
||||
/*expand_range=*/true,
|
||||
/*starting_l0_file=*/starting_l0_file);
|
||||
|
||||
// If we include more L0 files in the same compaction run it can
|
||||
// cause the 'smallest' and 'largest' key to get extended to a
|
||||
|
||||
@@ -203,13 +203,16 @@ class CompactionPicker {
|
||||
const std::vector<CompactionInputFiles>& inputs, int level,
|
||||
int proximal_level) const;
|
||||
|
||||
// @param starting_l0_file If not null, restricts L0 file selection to only
|
||||
// include files at or older than starting_l0_file.
|
||||
bool SetupOtherInputs(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* inputs,
|
||||
CompactionInputFiles* output_level_inputs,
|
||||
int* parent_index, int base_index,
|
||||
bool only_expand_towards_right = false);
|
||||
bool only_expand_towards_right = false,
|
||||
const FileMetaData* starting_l0_file = nullptr);
|
||||
|
||||
void GetGrandparents(VersionStorageInfo* vstorage,
|
||||
const CompactionInputFiles& inputs,
|
||||
@@ -222,9 +225,12 @@ class CompactionPicker {
|
||||
CompactionInputFiles* start_level_inputs,
|
||||
std::function<bool(const FileMetaData*)> skip_marked_file);
|
||||
|
||||
// @param starting_l0_file If not null, restricts L0 file selection to only
|
||||
// include files at or older than starting_l0_file.
|
||||
bool GetOverlappingL0Files(VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* start_level_inputs,
|
||||
int output_level, int* parent_index);
|
||||
int output_level, int* parent_index,
|
||||
const FileMetaData* starting_l0_file = nullptr);
|
||||
|
||||
// Register this compaction in the set of running compactions
|
||||
void RegisterCompaction(Compaction* c);
|
||||
|
||||
@@ -4716,6 +4716,53 @@ TEST_F(CompactionPickerTest, UniversalMaxReadAmpSmallDB) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, StandaloneRangeDeletionOnlyPicksOlderFiles) {
|
||||
NewVersionStorage(6, kCompactionStyleUniversal);
|
||||
|
||||
// Create L0 files with overlapping ranges
|
||||
// File 1: newest regular file (epoch 5), keys [100, 200]
|
||||
Add(0, 1U, "100", "200", 1U, 0, 100, 100, 0, false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 5);
|
||||
|
||||
// File 2: standalone range deletion (epoch 4), keys [150, 250]
|
||||
// This file should be marked as having only range deletions
|
||||
Add(0, 2U, "150", "250", 1U, 0, 200, 200, 0, true, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 4);
|
||||
|
||||
// Manually set file 2 as standalone range deletion
|
||||
FileMetaData* range_del_file = file_map_[2U].first;
|
||||
range_del_file->num_entries = 1;
|
||||
range_del_file->num_range_deletions = 1;
|
||||
ASSERT_TRUE(range_del_file->FileIsStandAloneRangeTombstone());
|
||||
|
||||
Add(4, 10U, "000", "400", 1U);
|
||||
Add(5, 20U, "000", "400", 100);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
ASSERT_TRUE(universal_compaction_picker.NeedsCompaction(vstorage_.get()));
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_,
|
||||
/*existing_snapshots=*/{}, /* snapshot_checker */ nullptr,
|
||||
vstorage_.get(), &log_buffer_));
|
||||
|
||||
ASSERT_NE(nullptr, compaction);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
// First input level should be L0 with only the standalone range del file
|
||||
// (file 2)
|
||||
ASSERT_EQ(0, compaction->level(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_TRUE(compaction->input(0, 0)->FileIsStandAloneRangeTombstone());
|
||||
|
||||
// Second input level should be L4 with file 10
|
||||
ASSERT_EQ(4, compaction->level(1));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(10U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -1544,9 +1544,18 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
}
|
||||
|
||||
if (output_level != 0) {
|
||||
// For standalone range deletion, we don't want to compact it with newer
|
||||
// L0 files that it doesn't cover.
|
||||
const FileMetaData* starting_l0_file =
|
||||
(start_level == 0 && start_level_inputs.size() == 1 &&
|
||||
start_level_inputs.files[0]->FileIsStandAloneRangeTombstone())
|
||||
? start_level_inputs.files[0]
|
||||
: nullptr;
|
||||
|
||||
if (start_level == 0) {
|
||||
if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs,
|
||||
output_level, nullptr)) {
|
||||
output_level, nullptr,
|
||||
starting_l0_file)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -1557,7 +1566,8 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
output_level_inputs.level = output_level;
|
||||
if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
|
||||
&start_level_inputs, &output_level_inputs,
|
||||
&parent_index, -1)) {
|
||||
&parent_index, -1, false,
|
||||
starting_l0_file)) {
|
||||
return nullptr;
|
||||
}
|
||||
inputs.push_back(start_level_inputs);
|
||||
|
||||
@@ -1047,6 +1047,7 @@ TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
|
||||
Destroy(options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.paranoid_file_checks = paranoid_file_check_enabled;
|
||||
options.verify_output_flags = VerifyOutputFlags::kVerifyNone;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
@@ -1101,6 +1102,87 @@ TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutputVerifyOutputFlags) {
|
||||
for (VerifyOutputFlags verify_output_flags :
|
||||
{VerifyOutputFlags::kVerifyNone,
|
||||
VerifyOutputFlags::kEnableForLocalCompaction |
|
||||
VerifyOutputFlags::kVerifyBlockChecksum,
|
||||
VerifyOutputFlags::kEnableForRemoteCompaction |
|
||||
VerifyOutputFlags::kVerifyBlockChecksum,
|
||||
VerifyOutputFlags::kEnableForRemoteCompaction |
|
||||
VerifyOutputFlags::kVerifyIteration,
|
||||
VerifyOutputFlags::kVerifyAll}) {
|
||||
SCOPED_TRACE(
|
||||
"verify_output_flags=" +
|
||||
std::to_string(static_cast<std::underlying_type_t<VerifyOutputFlags>>(
|
||||
verify_output_flags)));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
Destroy(options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.paranoid_file_checks = false;
|
||||
options.verify_output_flags = verify_output_flags;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Corrupt files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
// Corrupt very small range of bytes. This corruption is so small
|
||||
// that this isn't caught by default light-weight check
|
||||
ASSERT_OK(test::CorruptFile(env_, file_name, 0, 1,
|
||||
false /* verifyChecksum */));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
const bool is_enabled_for_remote_compaction =
|
||||
!!(verify_output_flags & VerifyOutputFlags::kEnableForRemoteCompaction);
|
||||
const bool should_verify_block_checksum =
|
||||
!!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
|
||||
const bool should_verify_iteration =
|
||||
!!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
if (is_enabled_for_remote_compaction &&
|
||||
(should_verify_block_checksum || should_verify_iteration)) {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
} else {
|
||||
// CompactRange() goes through if block checksum wasn't verified
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, TruncatedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
@@ -109,12 +109,12 @@ Status SubcompactionState::AddToOutput(
|
||||
const CompactionIterator& iter, bool use_proximal_output,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_table_last_internal_key) {
|
||||
const ParsedInternalKey& prev_iter_output_internal_key) {
|
||||
// update target output
|
||||
current_outputs_ =
|
||||
use_proximal_output ? &proximal_level_outputs_ : &compaction_outputs_;
|
||||
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func,
|
||||
prev_table_last_internal_key);
|
||||
prev_iter_output_internal_key);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -221,7 +221,7 @@ class SubcompactionState {
|
||||
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_table_last_internal_key);
|
||||
const ParsedInternalKey& prev_iter_output_internal_key);
|
||||
|
||||
// Close all compaction output files, both output_to_proximal_level outputs
|
||||
// and normal outputs.
|
||||
|
||||
@@ -675,30 +675,6 @@ TEST_F(DBBasicTest, Flush) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ManifestRollOver) {
|
||||
do {
|
||||
Options options;
|
||||
options.max_manifest_file_size = 10; // 10 bytes
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
{
|
||||
ASSERT_OK(Put(1, "manifest_key1", std::string(1000, '1')));
|
||||
ASSERT_OK(Put(1, "manifest_key2", std::string(1000, '2')));
|
||||
ASSERT_OK(Put(1, "manifest_key3", std::string(1000, '3')));
|
||||
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
|
||||
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_GT(manifest_after_flush, manifest_before_flush);
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
|
||||
// check if a new manifest file got inserted or not.
|
||||
ASSERT_EQ(std::string(1000, '1'), Get(1, "manifest_key1"));
|
||||
ASSERT_EQ(std::string(1000, '2'), Get(1, "manifest_key2"));
|
||||
ASSERT_EQ(std::string(1000, '3'), Get(1, "manifest_key3"));
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IdentityAcrossRestarts) {
|
||||
constexpr size_t kMinIdSize = 10;
|
||||
do {
|
||||
|
||||
@@ -710,12 +710,20 @@ class AlwaysTrueBitsBuilder : public FilterBitsBuilder {
|
||||
count_ = 0;
|
||||
// Interpreted as "always true" filter (0 probes over 1 byte of
|
||||
// payload, 5 bytes metadata)
|
||||
return Slice("\0\0\0\0\0\0", 6);
|
||||
return Slice("\0\0\0\0\0\0", kAlwaysTrueFilterBytes);
|
||||
}
|
||||
using FilterBitsBuilder::Finish;
|
||||
size_t ApproximateNumEntries(size_t) override { return SIZE_MAX; }
|
||||
size_t CalculateSpace(size_t /* num_entries */) override {
|
||||
return kAlwaysTrueFilterBytes;
|
||||
}
|
||||
double EstimatedFpRate(size_t /* num_entries */,
|
||||
size_t /* bytes */) override {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t kAlwaysTrueFilterBytes = 6;
|
||||
size_t count_ = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -11541,6 +11541,71 @@ TEST_F(DBCompactionTest, RecordNewestKeyTimeForTtlCompaction) {
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
}
|
||||
|
||||
// Test verifies compaction file cutting logic when using tail size estimation
|
||||
// maintains output files at or below the target file size.
|
||||
TEST_F(DBCompactionTest, CompactionRespectsTargetSizeWithTailEstimation) {
|
||||
const int kInitialKeyCount = 10000; // 10k keys
|
||||
const int kValueSize = 100; // 100 bytes per key
|
||||
const int kSeed = 301;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.target_file_size_is_upper_bound = true;
|
||||
options.target_file_size_base = 256 * 1024;
|
||||
options.write_buffer_size = 2 * 1024 * 1024;
|
||||
options.level0_file_num_compaction_trigger = 100; // Never trigger L0->L1
|
||||
options.compression = kNoCompression;
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.partition_filters = true;
|
||||
table_options.metadata_block_size = 4 * 1024;
|
||||
table_options.index_type = BlockBasedTableOptions::kBinarySearch;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Generate 2 L0 files
|
||||
// Generate first file with 10k keys (each ~100 bytes) approx 1.2MB total
|
||||
Random rnd(kSeed);
|
||||
for (int i = 0; i < kInitialKeyCount; i++) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Generate second file with overlapping keys to force compaction (prevent
|
||||
// trivial move)
|
||||
for (int i = kInitialKeyCount / 2; i < kInitialKeyCount * 1.5; i++) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Capture file metadata and assert two L0 files
|
||||
std::vector<LiveFileMetaData> file_metadata;
|
||||
db_->GetLiveFilesMetaData(&file_metadata);
|
||||
ASSERT_EQ(file_metadata.size(), 2);
|
||||
for (const auto& file : file_metadata) {
|
||||
ASSERT_EQ(file.level, 0);
|
||||
};
|
||||
|
||||
// Manually compact LO files to L1
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 1;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Verify that compacted output files are under target file size
|
||||
for (const auto& file : file_metadata) {
|
||||
if (file.level > 0) {
|
||||
EXPECT_LE(file.size, options.target_file_size_base)
|
||||
<< "Output file size exceeds target size: " << " File: " << file.name
|
||||
<< " level: " << file.level << " File size: " << file.size
|
||||
<< " Target size: " << options.target_file_size_base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PeriodicCompactionListener : public EventListener {
|
||||
public:
|
||||
explicit PeriodicCompactionListener() {}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBEtc3Test : public DBTestBase {
|
||||
public:
|
||||
DBEtc3Test() : DBTestBase("db_etc3_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
TEST_F(DBEtc3Test, ManifestRollOver) {
|
||||
do {
|
||||
Options options;
|
||||
// Force new manifest on each manifest write
|
||||
options.max_manifest_file_size = 0;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
{
|
||||
ASSERT_OK(Put(1, "key1", std::string(1000, '1')));
|
||||
ASSERT_OK(Put(1, "key2", std::string(1000, '2')));
|
||||
ASSERT_OK(Put(1, "key3", std::string(1000, '3')));
|
||||
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
|
||||
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_GT(manifest_after_flush, manifest_before_flush);
|
||||
// Re-open should always re-create manifest file
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
|
||||
ASSERT_EQ(std::string(1000, '1'), Get(1, "key1"));
|
||||
ASSERT_EQ(std::string(1000, '2'), Get(1, "key2"));
|
||||
ASSERT_EQ(std::string(1000, '3'), Get(1, "key3"));
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBEtc3Test, AutoTuneManifestSize) {
|
||||
// Ensure we have auto-tuning beyond max_manifest_file_size by default
|
||||
ASSERT_EQ(DBOptions{}.max_manifest_space_amp_pct, 500);
|
||||
|
||||
Options options = CurrentOptions();
|
||||
ASSERT_OK(db_->SetOptions({{"level0_file_num_compaction_trigger", "20"}}));
|
||||
|
||||
// Use large column family names to essentially control the amount of payload
|
||||
// data needed for the manifest file. Drop manifest entries don't include the
|
||||
// CF name so are small.
|
||||
uint64_t prev_manifest_num = 0, cur_manifest_num = 0;
|
||||
std::deque<ColumnFamilyHandle*> handles;
|
||||
int counter = 5;
|
||||
auto AddCfFn = [&]() {
|
||||
std::string name = "cf" + std::to_string(counter++);
|
||||
name.resize(1000, 'a');
|
||||
ASSERT_OK(db_->CreateColumnFamily(options, name, &handles.emplace_back()));
|
||||
prev_manifest_num = cur_manifest_num;
|
||||
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
};
|
||||
auto DropCfFn = [&]() {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles.front()));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles.front()));
|
||||
handles.pop_front();
|
||||
prev_manifest_num = cur_manifest_num;
|
||||
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
};
|
||||
auto TrivialManifestWriteFn = [&]() {
|
||||
ASSERT_OK(Put("x", std::to_string(counter++)));
|
||||
ASSERT_OK(Flush());
|
||||
prev_manifest_num = cur_manifest_num;
|
||||
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
};
|
||||
|
||||
options.max_manifest_file_size = 1000000;
|
||||
options.max_manifest_space_amp_pct = 0; // no auto-tuning yet
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// With the generous (minimum) maximum manifest size, should not be rotated
|
||||
AddCfFn();
|
||||
AddCfFn();
|
||||
AddCfFn();
|
||||
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// Change options for small max and (still) no auto-tuning
|
||||
ASSERT_OK(db_->SetDBOptions({{"max_manifest_file_size", "3000"}}));
|
||||
|
||||
// Takes effect on the next manifest write
|
||||
TrivialManifestWriteFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// Now we have to rewrite the whole manifest on each write because the
|
||||
// compacted size exceeds the "max" size.
|
||||
AddCfFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
DropCfFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
AddCfFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
TrivialManifestWriteFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// Enabling auto-tuning should fix this, immediately for next manifest writes.
|
||||
// This will allow up to double-ish the size of the compacted manifest,
|
||||
// which last should have been 4000 + some bytes.
|
||||
ASSERT_EQ(handles.size(), 4U);
|
||||
ASSERT_OK(db_->SetDBOptions({{"max_manifest_space_amp_pct", "105"}}));
|
||||
|
||||
// After 9 CF names should be enough to rotate the manifest
|
||||
for (int i = 1; i <= 5; ++i) {
|
||||
if ((i % 2) == 1) {
|
||||
DropCfFn();
|
||||
}
|
||||
AddCfFn();
|
||||
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
|
||||
}
|
||||
TrivialManifestWriteFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// We now have a different last compacted manifest size, should be
|
||||
// able to go beyond 9 CFs named in manifest this time.
|
||||
ASSERT_EQ(handles.size(), 6U);
|
||||
|
||||
DropCfFn();
|
||||
DropCfFn();
|
||||
for (int i = 1; i <= 4; ++i) {
|
||||
DropCfFn();
|
||||
AddCfFn();
|
||||
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
|
||||
}
|
||||
// We've written 10 named CFs to the manifest. We should be able to
|
||||
// dynamically change the auto-tuning still based on the last "compacted"
|
||||
// manifest size of 7000 + some bytes.
|
||||
ASSERT_OK(db_->SetDBOptions({{"max_manifest_space_amp_pct", "51"}}));
|
||||
TrivialManifestWriteFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
// And the "compacted" manifest size has reset again, so should be changed
|
||||
// again sooner.
|
||||
ASSERT_EQ(handles.size(), 4U);
|
||||
for (int i = 1; i <= 2; ++i) {
|
||||
AddCfFn();
|
||||
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
|
||||
}
|
||||
// Enough for manifest change
|
||||
AddCfFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// Wrap up
|
||||
while (!handles.empty()) {
|
||||
DropCfFn();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -109,6 +109,7 @@ Status DBImpl::GetSortedWalFilesImpl(VectorWalPtr& files, bool need_seqnos) {
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (pending_purge_obsolete_files_ > 0 || bg_purge_scheduled_ > 0) {
|
||||
TEST_SYNC_POINT("DBImpl::GetSortedWalFilesImpl:WaitPurge");
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
|
||||
|
||||
+20
-7
@@ -258,10 +258,10 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
[this]() { this->TriggerPeriodicCompaction(); });
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &immutable_db_options_, file_options_, table_cache_.get(),
|
||||
write_buffer_manager_, &write_controller_, &block_cache_tracer_,
|
||||
io_tracer_, db_id_, db_session_id_, options.daily_offpeak_time_utc,
|
||||
&error_handler_, read_only));
|
||||
dbname_, &immutable_db_options_, mutable_db_options_, file_options_,
|
||||
table_cache_.get(), write_buffer_manager_, &write_controller_,
|
||||
&block_cache_tracer_, io_tracer_, db_id_, db_session_id_,
|
||||
options.daily_offpeak_time_utc, &error_handler_, read_only));
|
||||
column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
|
||||
@@ -1412,7 +1412,7 @@ Status DBImpl::SetDBOptions(
|
||||
file_options_for_compaction_ = FileOptions(new_db_options);
|
||||
file_options_for_compaction_ = fs_->OptimizeForCompactionTableWrite(
|
||||
file_options_for_compaction_, immutable_db_options_);
|
||||
versions_->ChangeFileOptions(mutable_db_options_);
|
||||
versions_->UpdatedMutableDbOptions(mutable_db_options_, &mutex_);
|
||||
// TODO(xiez): clarify why apply optimize for read to write options
|
||||
file_options_for_compaction_ = fs_->OptimizeForCompactionTableRead(
|
||||
file_options_for_compaction_, immutable_db_options_);
|
||||
@@ -5047,6 +5047,19 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* metadata) {
|
||||
assert(column_family);
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
cfd->current()->GetColumnFamilyMetaData(options, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::GetAllColumnFamilyMetaData(
|
||||
std::vector<ColumnFamilyMetaData>* metadata) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -5506,7 +5519,7 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
|
||||
void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* cfd) const {
|
||||
if (immutable_db_options_.enable_thread_tracking) {
|
||||
@@ -5533,7 +5546,7 @@ void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
|
||||
void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
|
||||
|
||||
void DBImpl::EraseThreadStatusDbInfo() const {}
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
|
||||
//
|
||||
// A global method that can dump out the build version
|
||||
|
||||
@@ -573,6 +573,11 @@ class DBImpl : public DB {
|
||||
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
|
||||
// Get column family metadata with filtering based on key range and level
|
||||
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
|
||||
void GetAllColumnFamilyMetaData(
|
||||
std::vector<ColumnFamilyMetaData>* metadata) override;
|
||||
|
||||
@@ -2396,6 +2401,14 @@ class DBImpl : public DB {
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
CompactionJobInfo* compaction_job_info);
|
||||
|
||||
// Helper function to perform trivial move by updating manifest metadata
|
||||
// without rewriting data files. This is called when IsTrivialMove() is true.
|
||||
// REQUIRES: mutex held
|
||||
// Returns: Status of the trivial move operation
|
||||
Status PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
|
||||
bool& compaction_released, size_t& moved_files,
|
||||
size_t& moved_bytes);
|
||||
|
||||
// REQUIRES: mutex unlocked
|
||||
void TrackOrUntrackFiles(const std::vector<std::string>& existing_data_files,
|
||||
bool track);
|
||||
|
||||
@@ -1424,6 +1424,56 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
|
||||
bool& compaction_released,
|
||||
size_t& moved_files, size_t& moved_bytes) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Moving %d files to level-%d\n",
|
||||
c.column_family_data()->GetName().c_str(),
|
||||
static_cast<int>(c.num_input_files(0)), c.output_level());
|
||||
|
||||
// Move files to the output level by editing the manifest
|
||||
for (unsigned int l = 0; l < c.num_input_levels(); l++) {
|
||||
if (c.level(l) == c.output_level()) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < c.num_input_files(l); i++) {
|
||||
FileMetaData* f = c.input(l, i);
|
||||
c.edit()->DeleteFile(c.level(l), f->fd.GetNumber());
|
||||
c.edit()->AddFile(c.output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->epoch_number,
|
||||
f->file_checksum, f->file_checksum_func_name,
|
||||
f->unique_id, f->compensated_range_deletion_size,
|
||||
f->tail_size, f->user_defined_timestamps_persisted);
|
||||
moved_bytes += static_cast<size_t>(c.input(l, i)->fd.GetFileSize());
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer, "[%s] Moved #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
|
||||
c.column_family_data()->GetName().c_str(), f->fd.GetNumber(),
|
||||
c.output_level(), f->fd.GetFileSize());
|
||||
}
|
||||
moved_files += c.num_input_files(l);
|
||||
}
|
||||
|
||||
// Install the new version
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
const WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
Status status = versions_->LogAndApply(
|
||||
c.column_family_data(), read_options, write_options, c.edit(), &mutex_,
|
||||
directories_.GetDbDir(), /*new_descriptor_log=*/false,
|
||||
/*column_family_options=*/nullptr,
|
||||
[&c, &compaction_released](const Status& s) {
|
||||
c.ReleaseCompactionFiles(s);
|
||||
compaction_released = true;
|
||||
});
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::CompactFilesImpl(
|
||||
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
|
||||
Version* version, const std::vector<std::string>& input_file_names,
|
||||
@@ -1511,6 +1561,63 @@ Status DBImpl::CompactFilesImpl(
|
||||
// deletion compaction currently not allowed in CompactFiles.
|
||||
assert(!c->deletion_compaction());
|
||||
|
||||
// Check if this can be a trivial move (metadata-only update)
|
||||
// Similar to the logic in DBImpl::BackgroundCompaction
|
||||
// Note: We disable trivial move when compaction_service is present because
|
||||
// the service expects all compactions to go through CompactionJob for
|
||||
// tracking
|
||||
bool is_trivial_move = compact_options.allow_trivial_move &&
|
||||
c->IsTrivialMove() &&
|
||||
immutable_db_options().compaction_service == nullptr;
|
||||
|
||||
if (is_trivial_move) {
|
||||
// Perform trivial move: just update manifest without rewriting data
|
||||
TEST_SYNC_POINT("DBImpl::CompactFilesImpl:TrivialMove");
|
||||
|
||||
bool compaction_released = false;
|
||||
size_t moved_files = 0;
|
||||
size_t moved_bytes = 0;
|
||||
Status status = PerformTrivialMove(
|
||||
*c.get(), log_buffer, compaction_released, moved_files, moved_bytes);
|
||||
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), job_context->superversion_contexts.data());
|
||||
|
||||
// Populate output file names for trivial move
|
||||
if (output_file_names != nullptr) {
|
||||
for (const auto& newf : c->edit()->GetNewFiles()) {
|
||||
output_file_names->push_back(TableFileName(
|
||||
c->immutable_options().cf_paths, newf.second.fd.GetNumber(),
|
||||
newf.second.fd.GetPathId()));
|
||||
}
|
||||
}
|
||||
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] Trivial move succeeded for %zu files, %zu bytes total\n",
|
||||
c->column_family_data()->GetName().c_str(), moved_files, moved_bytes);
|
||||
} else {
|
||||
if (!compaction_released) {
|
||||
c->ReleaseCompactionFiles(status);
|
||||
}
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Trivial move failed: %s\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
status.ToString().c_str());
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
|
||||
c.reset();
|
||||
bg_compaction_scheduled_--;
|
||||
if (bg_compaction_scheduled_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Not a trivial move, proceed with full compaction
|
||||
InitSnapshotContext(job_context);
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem(
|
||||
@@ -4074,35 +4181,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
|
||||
// Move files to next level
|
||||
int32_t moved_files = 0;
|
||||
int64_t moved_bytes = 0;
|
||||
for (unsigned int l = 0; l < c->num_input_levels(); l++) {
|
||||
if (c->level(l) == c->output_level()) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < c->num_input_files(l); i++) {
|
||||
FileMetaData* f = c->input(l, i);
|
||||
c->edit()->DeleteFile(c->level(l), f->fd.GetNumber());
|
||||
c->edit()->AddFile(
|
||||
c->output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest, f->fd.smallest_seqno,
|
||||
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->epoch_number, f->file_checksum,
|
||||
f->file_checksum_func_name, f->unique_id,
|
||||
f->compensated_range_deletion_size, f->tail_size,
|
||||
f->user_defined_timestamps_persisted);
|
||||
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] Moving #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
|
||||
c->column_family_data()->GetName().c_str(), f->fd.GetNumber(),
|
||||
c->output_level(), f->fd.GetFileSize());
|
||||
++moved_files;
|
||||
moved_bytes += f->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
if (c->compaction_reason() == CompactionReason::kLevelMaxLevelSize &&
|
||||
c->immutable_options().compaction_pri == kRoundRobin) {
|
||||
int start_level = c->start_level();
|
||||
@@ -4113,14 +4191,12 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
vstorage->GetNextCompactCursor(start_level, c->num_input_files(0)));
|
||||
}
|
||||
}
|
||||
status = versions_->LogAndApply(
|
||||
c->column_family_data(), read_options, write_options, c->edit(),
|
||||
&mutex_, directories_.GetDbDir(),
|
||||
/*new_descriptor_log=*/false, /*column_family_options=*/nullptr,
|
||||
[&c, &compaction_released](const Status& s) {
|
||||
c->ReleaseCompactionFiles(s);
|
||||
compaction_released = true;
|
||||
});
|
||||
|
||||
// Perform the trivial move
|
||||
size_t moved_files = 0;
|
||||
size_t moved_bytes = 0;
|
||||
status = PerformTrivialMove(*c.get(), log_buffer, compaction_released,
|
||||
moved_files, moved_bytes);
|
||||
io_s = versions_->io_status();
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), job_context->superversion_contexts.data());
|
||||
@@ -4135,8 +4211,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
<< "total_files_size" << moved_bytes;
|
||||
}
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] Moved #%d files to level-%d %" PRIu64 " bytes %s: %s\n",
|
||||
log_buffer, "[%s] Moved #%d files to level-%zu %zu bytes %s: %s\n",
|
||||
c->column_family_data()->GetName().c_str(), moved_files,
|
||||
c->output_level(), moved_bytes, status.ToString().c_str(),
|
||||
c->column_family_data()->current()->storage_info()->LevelSummary(&tmp));
|
||||
|
||||
@@ -267,6 +267,9 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
if (!job_context->HaveSomethingToDelete()) {
|
||||
mutex_.AssertHeld();
|
||||
--pending_purge_obsolete_files_;
|
||||
if (pending_purge_obsolete_files_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -293,9 +293,9 @@ Status DB::OpenAsFollower(
|
||||
DBImplFollower* impl =
|
||||
new DBImplFollower(tmp_opts, std::move(new_env), dbname, src_path);
|
||||
impl->versions_.reset(new ReactiveVersionSet(
|
||||
dbname, &impl->immutable_db_options_, impl->file_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
&impl->write_controller_, impl->io_tracer_));
|
||||
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
|
||||
impl->file_options_, impl->table_cache_.get(),
|
||||
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
|
||||
@@ -329,7 +329,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
}
|
||||
FileTypeSet tmp_set = immutable_db_options_.checksum_handoff_file_types;
|
||||
file->SetPreallocationBlockSize(
|
||||
immutable_db_options_.manifest_preallocation_size);
|
||||
mutable_db_options_.manifest_preallocation_size);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), manifest, file_options, immutable_db_options_.clock,
|
||||
io_tracer_, nullptr /* stats */,
|
||||
|
||||
@@ -783,9 +783,9 @@ Status DB::OpenAsSecondary(
|
||||
handles->clear();
|
||||
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname, secondary_path);
|
||||
impl->versions_.reset(new ReactiveVersionSet(
|
||||
dbname, &impl->immutable_db_options_, impl->file_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
&impl->write_controller_, impl->io_tracer_));
|
||||
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
|
||||
impl->file_options_, impl->table_cache_.get(),
|
||||
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
@@ -1102,11 +1102,6 @@ Status DBImplSecondary::InitializeCompactionWorkspace(
|
||||
return s;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Initialized compaction workspace with %zu subcompaction "
|
||||
"progress to resume",
|
||||
compaction_progress_.size());
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -1219,6 +1214,11 @@ Status DBImplSecondary::PrepareCompactionProgressState() {
|
||||
return HandleInvalidOrNoCompactionProgress(compaction_progress_file_path,
|
||||
scan_result);
|
||||
}
|
||||
|
||||
ROCKS_LOG_DEBUG(
|
||||
immutable_db_options_.info_log,
|
||||
"Loaded compaction progress with %zu subcompaction(s) from %s",
|
||||
compaction_progress_.size(), compaction_progress_file_path.c_str());
|
||||
return s;
|
||||
} else {
|
||||
return HandleInvalidOrNoCompactionProgress(
|
||||
@@ -1299,24 +1299,33 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
}
|
||||
Status s;
|
||||
|
||||
// TODO(hx235): Resuming compaction is currently incompatible with
|
||||
// paranoid_file_checks=true because OutputValidator hash verification would
|
||||
// fail during compaction resumption. Before interruption, resuming
|
||||
// compaction needs to persist the hash of each output file to enable
|
||||
// validation after resumption. Alternatively and preferably, we could move
|
||||
// the output verification to happen immediately after each output file is
|
||||
// created. This workaround currently disables resuming compaction when
|
||||
// paranoid_file_checks is enabled. Note that paranoid_file_checks is
|
||||
// disabled by default.
|
||||
bool allow_resumption =
|
||||
options.allow_resumption &&
|
||||
!cfd->GetLatestMutableCFOptions().paranoid_file_checks;
|
||||
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
|
||||
if (options.allow_resumption &&
|
||||
cfd->GetLatestMutableCFOptions().paranoid_file_checks) {
|
||||
// TODO(hx235): Resuming compaction is currently incompatible with
|
||||
// output hash verification (enabled via paranoid_file_checks=true or
|
||||
// verify_output_flags containing kVerifyIteration) because resumed compaction
|
||||
// will lose the hash computed before interruption.
|
||||
// Potential solutions:
|
||||
// 1. Persist the hash state: Before interruption, save the current hash value
|
||||
// of each output file to disk, allowing validation to continue correctly
|
||||
// after resumption.
|
||||
// 2. Immediate verification: Move output verification to happen
|
||||
// immediately after each output file is created and closed, eliminating
|
||||
// the need to maintain hash state across resumption boundaries.
|
||||
bool output_hash_verification_enabled =
|
||||
mutable_cf_options.paranoid_file_checks ||
|
||||
!!(mutable_cf_options.verify_output_flags &
|
||||
VerifyOutputFlags::kVerifyIteration);
|
||||
|
||||
bool allow_resumption =
|
||||
options.allow_resumption && !output_hash_verification_enabled;
|
||||
|
||||
if (options.allow_resumption && output_hash_verification_enabled) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Resume compaction configured but disabled due to "
|
||||
"incompatible with paranoid_file_checks=true");
|
||||
"incompatibility with output hash verification "
|
||||
"(paranoid_file_checks=true or verify_output_flags "
|
||||
"containing kVerifyIteration)");
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
@@ -1345,8 +1354,8 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
CompactionOptions comp_options;
|
||||
comp_options.compression = kDisableCompressionOption;
|
||||
comp_options.output_file_size_limit = MaxFileSizeForLevel(
|
||||
cfd->GetLatestMutableCFOptions(), input.output_level,
|
||||
cfd->ioptions().compaction_style, vstorage->base_level(),
|
||||
mutable_cf_options, input.output_level, cfd->ioptions().compaction_style,
|
||||
vstorage->base_level(),
|
||||
cfd->ioptions().level_compaction_dynamic_level_bytes);
|
||||
|
||||
std::vector<CompactionInputFiles> input_files;
|
||||
@@ -1384,8 +1393,8 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
}
|
||||
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
|
||||
comp_options, input_files, input.output_level, vstorage,
|
||||
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0,
|
||||
earliest_snapshot, job_context.snapshot_checker));
|
||||
mutable_cf_options, mutable_db_options_, 0, earliest_snapshot,
|
||||
job_context.snapshot_checker));
|
||||
assert(c != nullptr);
|
||||
c->FinalizeInputInfo(version);
|
||||
|
||||
@@ -1731,6 +1740,11 @@ Status DBImplSecondary::FinalizeCompactionProgressWriter(
|
||||
return HandleCompactionProgressWriterCreationFailure(
|
||||
"" /* temp_file_path */, final_file_path, compaction_progress_writer);
|
||||
}
|
||||
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"Finalized compaction progress writer onto %s",
|
||||
final_file_path.c_str());
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -4154,6 +4154,9 @@ INSTANTIATE_TEST_CASE_P(DBMultiScanIteratorTest, DBMultiScanIteratorTest,
|
||||
::testing::Bool());
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, BasicTest) {
|
||||
auto options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create a file
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
@@ -4196,6 +4199,8 @@ TEST_P(DBMultiScanIteratorTest, BasicTest) {
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, MixedBoundsTest) {
|
||||
auto options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
// Create a file
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
@@ -4602,6 +4607,11 @@ TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
dbfull()->GetColumnFamilyMetaData(cfh, &cf_meta);
|
||||
// Only the L0 with range deletion is compacted.
|
||||
ASSERT_EQ(1, cf_meta.levels[0].files.size());
|
||||
ASSERT_EQ(0, cf_meta.levels[0].files[0].num_deletions);
|
||||
|
||||
// The first scan range overlaps the DB key range, while the second extends
|
||||
// beyond but overlaps the delete range
|
||||
@@ -4666,6 +4676,357 @@ TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, ReseekAcrossBlocksSameUserKey) {
|
||||
// This test exposes a bug where multiscan reseeks backwards when
|
||||
// max_sequential_skip_in_iterations is triggered with the same user key
|
||||
// spanning multiple data blocks.
|
||||
|
||||
auto options = CurrentOptions();
|
||||
options.max_sequential_skip_in_iterations = 3;
|
||||
options.compression = kNoCompression;
|
||||
|
||||
// Force each internal key into its own block
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.flush_block_policy_factory =
|
||||
std::make_shared<FlushBlockEveryKeyPolicyFactory>();
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Taking a snapshot after each Put to preserve all versions during flush.
|
||||
std::vector<const Snapshot*> snapshots;
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
ASSERT_OK(Put("key_a", "value_" + std::to_string(i)));
|
||||
snapshots.push_back(db_->GetSnapshot());
|
||||
}
|
||||
ASSERT_OK(Put("key_b", "value_b"));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
// Setup multiscan range covering both keys
|
||||
std::vector<std::string> key_ranges({"key_a", "key_c"});
|
||||
ReadOptions ro;
|
||||
Slice ub = key_ranges[1];
|
||||
ro.iterate_upper_bound = &ub;
|
||||
ro.fill_cache = GetParam();
|
||||
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
|
||||
ASSERT_NE(iter, nullptr);
|
||||
iter->Prepare(scan_options);
|
||||
|
||||
std::vector<std::string> seen_keys;
|
||||
std::vector<std::string> seen_values;
|
||||
iter->Seek(key_ranges[0]);
|
||||
while (iter->status().ok() && iter->Valid()) {
|
||||
seen_keys.push_back(iter->key().ToString());
|
||||
seen_values.push_back(iter->value().ToString());
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status()) << iter->status().ToString();
|
||||
|
||||
ASSERT_EQ(seen_keys.size(), 2) << "Should see key_a and key_b";
|
||||
ASSERT_EQ(seen_keys[0], "key_a");
|
||||
ASSERT_EQ(seen_keys[1], "key_b");
|
||||
ASSERT_EQ(seen_values[0], "value_6");
|
||||
ASSERT_EQ(seen_values[1], "value_b");
|
||||
|
||||
for (auto* snapshot : snapshots) {
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchAcrossMultipleFiles) {
|
||||
// Test async prefetch with multiple ranges within a single file
|
||||
auto options = CurrentOptions();
|
||||
options.target_file_size_base = 1 << 15; // 32KiB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
options.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(303);
|
||||
|
||||
// Create a single large file with many keys
|
||||
// ~1MiB of data
|
||||
// Should be lots of files now
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << "k" << std::setw(5) << std::setfill('0') << i;
|
||||
// 1KiB values
|
||||
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
|
||||
ASSERT_GT(NumTableFilesAtLevel(49), 3);
|
||||
|
||||
// Set up multiple non-overlapping ranges in the same file
|
||||
// Every 32 values should be a file or so
|
||||
std::vector<std::string> key_ranges(
|
||||
{"k00000", "k00100", "k00500", "k00600", "k00800", "k00900"});
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.use_async_io = true;
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
scan_options.insert(key_ranges[4], key_ranges[5]);
|
||||
|
||||
auto read_count_before =
|
||||
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT);
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
ASSERT_NE(iter, nullptr);
|
||||
auto read_count_after =
|
||||
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT);
|
||||
ASSERT_EQ(read_count_after, read_count_before);
|
||||
|
||||
// Verify all three ranges can be scanned successfully
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
it.first.ToString();
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
// Make sure exception contains the status
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchMultipleLevels) {
|
||||
// Test async prefetch with files in L0 and non-L0 levels
|
||||
// Similar setup to AsyncPrefetchAcrossMultipleFiles but with L0 files
|
||||
auto options = CurrentOptions();
|
||||
options.target_file_size_base = 1 << 15; // 32KiB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
options.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(304);
|
||||
|
||||
// Create base files and compact to bottom level - ~500KiB of data
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << "k" << std::setw(5) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
|
||||
// Verify we have files at bottom level
|
||||
ASSERT_GT(NumTableFilesAtLevel(49), 0);
|
||||
|
||||
// Create additional L0 files with overlapping key ranges
|
||||
for (int i = 100; i < 150; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << "k" << std::setw(5) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Verify we now have files in both L0 and bottom level
|
||||
ASSERT_GT(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(49), 0);
|
||||
|
||||
// Set up multiple non-overlapping ranges
|
||||
std::vector<std::string> key_ranges(
|
||||
{"k00000", "k00100", "k00200", "k00300", "k00400", "k00500"});
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.use_async_io = true;
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
scan_options.insert(key_ranges[4], key_ranges[5]);
|
||||
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
ASSERT_NE(iter, nullptr);
|
||||
|
||||
// Verify all three ranges can be scanned successfully
|
||||
int total_keys = 0;
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
it.first.ToString();
|
||||
total_keys++;
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
|
||||
// Should have keys from all three ranges
|
||||
ASSERT_GT(total_keys, 0);
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithDeleteRange) {
|
||||
// Test async prefetch with delete ranges
|
||||
auto options = CurrentOptions();
|
||||
options.target_file_size_base = 1 << 15; // 32KiB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(305);
|
||||
|
||||
// Create base data - ~500KiB
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << "k" << std::setw(5) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Add delete ranges
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), dbfull()->DefaultColumnFamily(),
|
||||
"k00100", "k00200"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_GT(NumTableFilesAtLevel(49), 0);
|
||||
|
||||
// Set up scan ranges that interact with delete ranges
|
||||
std::vector<std::string> key_ranges({"k00000", "k00500"});
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.use_async_io = true;
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
ASSERT_NE(iter, nullptr);
|
||||
|
||||
// Verify ranges can be scanned successfully
|
||||
int total_keys = 0;
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
std::string key = it.first.ToString();
|
||||
// Verify deleted keys are not returned
|
||||
ASSERT_TRUE((key < "k00100" || key >= "k00200"));
|
||||
total_keys++;
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
|
||||
// Should have keys excluding deleted ranges
|
||||
ASSERT_EQ(total_keys, 400);
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, AsyncPrefetchWithExternalFileIngestion) {
|
||||
// Test async prefetch with externally ingested files
|
||||
auto options = CurrentOptions();
|
||||
options.target_file_size_base = 1 << 15; // 32KiB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(306);
|
||||
|
||||
// Create base data - ~200KiB
|
||||
for (int i = 0; i < 200; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << "k" << std::setw(5) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put(ss.str(), rnd.RandomString(1 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
|
||||
// Create and ingest external SST file with new data
|
||||
std::string ingest_file = dbname_ + "/test_ingest.sst";
|
||||
{
|
||||
std::unique_ptr<SstFileWriter> writer;
|
||||
writer.reset(new SstFileWriter(EnvOptions(), options));
|
||||
ASSERT_OK(writer->Open(ingest_file));
|
||||
for (int i = 300; i < 500; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << "k" << std::setw(5) << std::setfill('0') << i;
|
||||
ASSERT_OK(writer->Put(ss.str(), rnd.RandomString(1 << 10)));
|
||||
}
|
||||
ASSERT_OK(writer->Finish());
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
ASSERT_OK(dbfull()->IngestExternalFile(cfh, {ingest_file}, ifo));
|
||||
|
||||
// Set up scan ranges that span both regular and ingested files
|
||||
std::vector<std::string> key_ranges({"k00000", "k00500"});
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.use_async_io = true;
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
ASSERT_NE(iter, nullptr);
|
||||
|
||||
// Verify all ranges can be scanned successfully
|
||||
int total_keys = 0;
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
it.first.ToString();
|
||||
total_keys++;
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
|
||||
ASSERT_EQ(total_keys, 400);
|
||||
iter.reset();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -432,12 +432,47 @@ TEST_F(DBOptionsTest, SetWalBytesPerSync) {
|
||||
ASSERT_GT(low_bytes_per_sync, counter);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, MutableManifestOptions) {
|
||||
// These aren't end-to-end tests, but sufficient to ensure the VersionSet
|
||||
// receives the updates with SetDBOptions
|
||||
for (int64_t i : {0, 1, 100, 100000, 10000000}) {
|
||||
ASSERT_OK(
|
||||
db_->SetDBOptions({{"max_manifest_file_size", std::to_string(i)}}));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(db_->GetDBOptions().max_manifest_file_size));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetMinMaxManifestFileSize()));
|
||||
if (i > 1) {
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(
|
||||
db_->SetDBOptions({{"max_manifest_space_amp_pct", std::to_string(i)}}));
|
||||
ASSERT_EQ(i, static_cast<int64_t>(
|
||||
db_->GetDBOptions().max_manifest_space_amp_pct));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetMaxManifestSpaceAmpPct()));
|
||||
if (i > 1) {
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(db_->SetDBOptions(
|
||||
{{"manifest_preallocation_size", std::to_string(i)}}));
|
||||
ASSERT_EQ(i, static_cast<int64_t>(
|
||||
db_->GetDBOptions().manifest_preallocation_size));
|
||||
ASSERT_EQ(
|
||||
i, static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetManifestPreallocationSize()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, WritableFileMaxBufferSize) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 1024 * 1024;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.env = env_;
|
||||
int buffer_size = 1024 * 1024;
|
||||
Reopen(options);
|
||||
|
||||
@@ -3825,6 +3825,89 @@ TEST_F(DBRangeDelTest, RowCache) {
|
||||
// and should not turn db into read-only mdoe.
|
||||
ASSERT_OK(Put(Key(5), "foo"));
|
||||
}
|
||||
|
||||
TEST_F(DBRangeDelTest, SeekForPrevTest) {
|
||||
// open db
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
|
||||
// add SST partitioner, split sst file with prefix length 2
|
||||
options.sst_partitioner_factory = NewSstPartitionerFixedPrefixFactory(2);
|
||||
Reopen(options);
|
||||
|
||||
// File uses SST partitioner, so it will be split into 3 files
|
||||
// SST file 1: ka1, ka2
|
||||
// SST file 2: kb1
|
||||
// SST file 3: kc1, kc2
|
||||
// Delete range covers from ka2 to kc2, which means record ka2 and kb1, kc1
|
||||
// are covered by the delete range
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> kv = {{"ka1", "value_1"},
|
||||
{"ka2", "value_2"},
|
||||
{"kb1", "value_3"},
|
||||
{"kc1", "value_4"},
|
||||
{"kc2", "value_5"}};
|
||||
for (auto& p : kv) {
|
||||
ASSERT_OK(Put(p.first, p.second));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
// Compact to Lmax, it should have seq 0 now.
|
||||
ASSERT_OK(CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// Open an iterator and create a snapshot, so that keys are not deleted
|
||||
// completely by delete range in SST
|
||||
ReadOptions read_opts;
|
||||
read_opts.snapshot = db_->GetSnapshot();
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_opts));
|
||||
iter->SeekToFirst();
|
||||
// iterate all the keys and validate the value
|
||||
for (int i = 0; iter->Valid(); iter->Next()) {
|
||||
ASSERT_EQ(kv[i].first, iter->key().ToString());
|
||||
ASSERT_EQ(kv[i].second, iter->value().ToString());
|
||||
i++;
|
||||
}
|
||||
|
||||
// use delete range to delete the record
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "ka2",
|
||||
"kc2"));
|
||||
// Flush
|
||||
ASSERT_OK(Flush());
|
||||
// Compact to Lmax
|
||||
ASSERT_OK(CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// Close the iterator and release the snapshot.
|
||||
ASSERT_OK(iter->status());
|
||||
iter.reset();
|
||||
db_->ReleaseSnapshot(read_opts.snapshot);
|
||||
|
||||
// create second iterator, seek each key and validate result
|
||||
std::unique_ptr<Iterator> iter2(db_->NewIterator(ReadOptions()));
|
||||
// Validate keys are deleted
|
||||
iter2->SeekToFirst();
|
||||
ASSERT_TRUE(iter2->Valid());
|
||||
ASSERT_EQ("ka1", iter2->key().ToString());
|
||||
iter2->Next();
|
||||
ASSERT_TRUE(iter2->Valid());
|
||||
ASSERT_EQ("kc2", iter2->key().ToString());
|
||||
iter2->Next();
|
||||
ASSERT_FALSE(iter2->Valid());
|
||||
|
||||
// Validate seek for prev result
|
||||
for (auto& p : kv) {
|
||||
iter2->SeekForPrev(p.first);
|
||||
ASSERT_TRUE(iter2->Valid());
|
||||
if (p.first == "kc2") {
|
||||
ASSERT_EQ("kc2", iter2->key().ToString());
|
||||
} else {
|
||||
ASSERT_EQ("ka1", iter2->key().ToString());
|
||||
}
|
||||
}
|
||||
ASSERT_OK(iter2->status());
|
||||
iter2.reset();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+249
-4
@@ -1492,6 +1492,246 @@ TEST_F(DBTest, MetaDataTest) {
|
||||
CheckLiveFilesMeta(live_file_meta, files_by_level);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetColumnFamilyMetaDataWithKeyRangeAndLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
int64_t temp_time = 0;
|
||||
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_index = 0;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
// Add a single blob reference to each file
|
||||
std::string blob_index;
|
||||
BlobIndex::EncodeBlob(&blob_index, /* blob_file_number */ i + 1000,
|
||||
/* offset */ 1234, /* size */ 5678, kNoCompression);
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, Key(key_index),
|
||||
blob_index));
|
||||
ASSERT_OK(dbfull()->Write(WriteOptions(), &batch));
|
||||
|
||||
++key_index;
|
||||
|
||||
// Fill up the rest of the file with random values.
|
||||
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files_by_level;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files_by_level);
|
||||
|
||||
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
// Keys in the SST files are distributed
|
||||
// (key000000, key000100) ->File 1
|
||||
// (key000101, key000201) -> File 2
|
||||
// (key000202, key000302) -> File 3
|
||||
// (key009999, key010099) -> File 100
|
||||
|
||||
// With keySlice (key000050, key000150) => should only pick 2 files(instead of
|
||||
// default 100 that is in the level)
|
||||
auto startKey = Slice("key000050");
|
||||
auto endKey = Slice("key000150");
|
||||
GetColumnFamilyMetaDataOptions cf_options(startKey, endKey, 0);
|
||||
db_->GetColumnFamilyMetaData(cf_options, &cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
const auto& level_meta_from_cf = cf_meta.levels[0];
|
||||
ASSERT_EQ(level_meta_from_cf.files.size(), 2);
|
||||
ASSERT_LT(level_meta_from_cf.files[1].smallestkey,
|
||||
std::string(startKey.data()));
|
||||
ASSERT_GT(level_meta_from_cf.files[0].largestkey, std::string(endKey.data()));
|
||||
|
||||
GetColumnFamilyMetaDataOptions cf_option_default;
|
||||
db_->GetColumnFamilyMetaData(cf_option_default, &cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].files.size(), 100);
|
||||
|
||||
// Test with start key valid and end key unbounded
|
||||
// This should get all files from key000150 onwards (99 files)
|
||||
auto startKeyUnbounded = Slice("key000150");
|
||||
GetColumnFamilyMetaDataOptions cf_options_unbounded_end(startKeyUnbounded,
|
||||
OptSlice(), 0);
|
||||
db_->GetColumnFamilyMetaData(cf_options_unbounded_end, &cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].files.size(), 99);
|
||||
|
||||
// Test with end key valid and start key unbounded
|
||||
// This should get all files from beginning to key000250 ( 3 files)
|
||||
auto endKeyUnbounded = Slice("key000250");
|
||||
GetColumnFamilyMetaDataOptions cf_options_unbounded_start(OptSlice(),
|
||||
endKeyUnbounded, 0);
|
||||
db_->GetColumnFamilyMetaData(cf_options_unbounded_start, &cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].files.size(), 3);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetColumnFamilyMetaDataBottommostLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_index = 0;
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 6;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
|
||||
// Nothing on Level 0 after compaction
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
GetColumnFamilyMetaDataOptions cf_options_0(OptSlice(), OptSlice(), 0);
|
||||
db_->GetColumnFamilyMetaData(cf_options_0, &cf_meta);
|
||||
|
||||
ASSERT_EQ(cf_meta.levels.size(), 0);
|
||||
ASSERT_EQ(cf_meta.file_count, 0);
|
||||
|
||||
// Data should be in Level 6
|
||||
GetColumnFamilyMetaDataOptions cf_options(OptSlice(), OptSlice(), 6);
|
||||
db_->GetColumnFamilyMetaData(cf_options, &cf_meta);
|
||||
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].level, 6);
|
||||
ASSERT_GT(cf_meta.levels[0].files.size(), 0);
|
||||
size_t all_files = cf_meta.levels[0].files.size();
|
||||
|
||||
// Keys in the SST files are distributed across level 6
|
||||
// Test with key range - should only return files within the range
|
||||
auto startKey = Slice("key000050");
|
||||
auto endKey = Slice("key000150");
|
||||
GetColumnFamilyMetaDataOptions cf_options_range(startKey, endKey, 6);
|
||||
db_->GetColumnFamilyMetaData(cf_options_range, &cf_meta);
|
||||
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].level, 6);
|
||||
ASSERT_GT(cf_meta.levels[0].files.size(), 0);
|
||||
size_t files_in_range = cf_meta.levels[0].files.size();
|
||||
|
||||
// Files in range should be less than or equal to all files
|
||||
ASSERT_LE(files_in_range, all_files);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetColumnFamilyMetaDataMultipleLevels) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_index = 0;
|
||||
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 6;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// First verify both levels have files without key range filter
|
||||
ColumnFamilyMetaData cf_meta_all_no_range;
|
||||
GetColumnFamilyMetaDataOptions cf_options_all_no_range;
|
||||
db_->GetColumnFamilyMetaData(cf_options_all_no_range, &cf_meta_all_no_range);
|
||||
|
||||
bool has_level_0 = false;
|
||||
bool has_level_6 = false;
|
||||
for (const auto& level : cf_meta_all_no_range.levels) {
|
||||
if (level.level == 0 && level.files.size() > 0) {
|
||||
has_level_0 = true;
|
||||
}
|
||||
if (level.level == 6 && level.files.size() > 0) {
|
||||
has_level_6 = true;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(has_level_0);
|
||||
ASSERT_TRUE(has_level_6);
|
||||
|
||||
// Test querying bottommost level only with key range
|
||||
// Use a range that should be in the first set of files (now in level 6)
|
||||
auto startKey = Slice("key000050");
|
||||
auto endKey = Slice("key000150");
|
||||
ColumnFamilyMetaData cf_meta_bottommost;
|
||||
GetColumnFamilyMetaDataOptions cf_options_bottommost(startKey, endKey, 6);
|
||||
db_->GetColumnFamilyMetaData(cf_options_bottommost, &cf_meta_bottommost);
|
||||
|
||||
ASSERT_EQ(cf_meta_bottommost.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta_bottommost.levels[0].level, 6);
|
||||
ASSERT_GT(cf_meta_bottommost.levels[0].files.size(), 0);
|
||||
size_t level_6_files_in_range = cf_meta_bottommost.levels[0].files.size();
|
||||
|
||||
// Test querying all levels with same key range
|
||||
ColumnFamilyMetaData cf_meta_all;
|
||||
GetColumnFamilyMetaDataOptions cf_options_all(startKey, endKey);
|
||||
db_->GetColumnFamilyMetaData(cf_options_all, &cf_meta_all);
|
||||
|
||||
size_t level_6_files_in_range_from_all = 0;
|
||||
for (const auto& level : cf_meta_all.levels) {
|
||||
if (level.level == 6) {
|
||||
level_6_files_in_range_from_all = level.files.size();
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_GT(level_6_files_in_range_from_all, 0);
|
||||
ASSERT_EQ(level_6_files_in_range, level_6_files_in_range_from_all);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetColumnFamilyMetaDataEmptyDB) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 7;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Test on empty database
|
||||
ColumnFamilyMetaData cf_meta_empty_db;
|
||||
GetColumnFamilyMetaDataOptions cf_options_empty_db;
|
||||
db_->GetColumnFamilyMetaData(cf_options_empty_db, &cf_meta_empty_db);
|
||||
|
||||
ASSERT_EQ(cf_meta_empty_db.levels.size(), 0);
|
||||
ASSERT_EQ(cf_meta_empty_db.file_count, 0);
|
||||
ASSERT_EQ(cf_meta_empty_db.size, 0);
|
||||
|
||||
// Test on empty database with key range
|
||||
auto startKey = Slice("key000050");
|
||||
auto endKey = Slice("key000150");
|
||||
ColumnFamilyMetaData cf_meta_empty_range;
|
||||
GetColumnFamilyMetaDataOptions cf_options_empty_range(startKey, endKey);
|
||||
db_->GetColumnFamilyMetaData(cf_options_empty_range, &cf_meta_empty_range);
|
||||
|
||||
ASSERT_EQ(cf_meta_empty_range.levels.size(), 0);
|
||||
ASSERT_EQ(cf_meta_empty_range.file_count, 0);
|
||||
ASSERT_EQ(cf_meta_empty_range.size, 0);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, AllMetaDataTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
@@ -3535,6 +3775,11 @@ class ModelDB : public DB {
|
||||
void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) override {}
|
||||
|
||||
void GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const GetColumnFamilyMetaDataOptions& /*options*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) override {}
|
||||
|
||||
Status GetDbIdentity(std::string& /*identity*/) const override {
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -4934,7 +5179,7 @@ TEST_F(DBTest, DynamicMemtableOptions) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
namespace {
|
||||
bool VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
|
||||
int expected_count) {
|
||||
@@ -5392,7 +5637,7 @@ TEST_P(DBTestWithParam, PreShutdownCompactionMiddle) {
|
||||
ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
|
||||
TEST_F(DBTest, FlushOnDestroy) {
|
||||
WriteOptions wo;
|
||||
@@ -6127,9 +6372,9 @@ TEST_F(DBTest, MergeTestTime) {
|
||||
|
||||
ASSERT_EQ(1, count);
|
||||
ASSERT_EQ(4000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
ASSERT_GT(TestGetTickerCount(options, FLUSH_WRITE_BYTES), 0);
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
}
|
||||
|
||||
TEST_P(DBTestWithParam, MergeCompactionTimeTest) {
|
||||
|
||||
@@ -5205,6 +5205,7 @@ TEST_F(DBTest2, SwitchMemtableRaceWithNewManifest) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
options.max_manifest_file_size = 10;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.create_if_missing = true;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
@@ -5896,6 +5897,7 @@ TEST_P(RenameCurrentTest, Flush) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
@@ -5915,6 +5917,7 @@ TEST_P(RenameCurrentTest, Compaction) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("a", "a_value"));
|
||||
|
||||
+2
-1
@@ -454,7 +454,8 @@ Options DBTestBase::GetOptions(
|
||||
options.allow_mmap_reads = can_allow_mmap;
|
||||
break;
|
||||
case kManifestFileSize:
|
||||
options.max_manifest_file_size = 50; // 50 bytes
|
||||
options.max_manifest_file_size = 50; // 50 bytes
|
||||
options.max_manifest_space_amp_pct = 0; // old behavior
|
||||
break;
|
||||
case kPerfOptions:
|
||||
options.delayed_write_rate = 8 * 1024 * 1024;
|
||||
|
||||
+3
-2
@@ -1746,8 +1746,8 @@ class RecoveryTestHelper {
|
||||
WriteController write_controller;
|
||||
|
||||
versions.reset(new VersionSet(
|
||||
test->dbname_, &db_options, file_options, table_cache.get(),
|
||||
&write_buffer_manager, &write_controller,
|
||||
test->dbname_, &db_options, MutableDBOptions{options}, file_options,
|
||||
table_cache.get(), &write_buffer_manager, &write_controller,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
|
||||
options.daily_offpeak_time_utc,
|
||||
@@ -2277,6 +2277,7 @@ TEST_F(DBWALTest, FixSyncWalOnObseletedWalWithNewManifestCausingMissingWAL) {
|
||||
Options options = CurrentOptions();
|
||||
// Small size to force manifest creation
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
@@ -142,13 +142,13 @@ class FlushJobTestBase : public testing::Test {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, MutableDBOptions{options_}, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
}
|
||||
|
||||
|
||||
+7
-17
@@ -163,9 +163,7 @@ TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
|
||||
options.max_bytes_for_level_base = options.target_file_size_base * 2;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.compression = kNoCompression;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.table_properties_collector_factories.push_back(
|
||||
std::make_shared<TestPropertiesCollectorFactory>());
|
||||
@@ -229,7 +227,7 @@ class TestFlushListener : public EventListener {
|
||||
ASSERT_EQ(info.file_checksum, kUnknownFileChecksum);
|
||||
ASSERT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName);
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
// Verify the id of the current thread that created this table
|
||||
// file matches the id of any active flush or compaction thread.
|
||||
uint64_t thread_id = env_->GetThreadID();
|
||||
@@ -246,7 +244,7 @@ class TestFlushListener : public EventListener {
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_match);
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
}
|
||||
|
||||
void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
|
||||
@@ -310,9 +308,7 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.write_buffer_size = k110KB;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
TestFlushListener* listener = new TestFlushListener(options.env, this);
|
||||
options.listeners.emplace_back(listener);
|
||||
std::vector<std::string> cf_names = {"pikachu", "ilya", "muromec",
|
||||
@@ -357,9 +353,7 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.write_buffer_size = k110KB;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
@@ -407,9 +401,7 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
options.table_properties_collector_factories.push_back(
|
||||
std::make_shared<TestPropertiesCollectorFactory>());
|
||||
std::vector<TestFlushListener*> listeners;
|
||||
@@ -497,9 +489,7 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
TestFlushListener* listener = new TestFlushListener(options.env, this);
|
||||
const int kCompactionTrigger = 1;
|
||||
const int kSlowdownTrigger = 5;
|
||||
|
||||
@@ -112,7 +112,8 @@ class MemTableListTest : public testing::Test {
|
||||
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
|
||||
WriteController write_controller(10000000u);
|
||||
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
VersionSet versions(dbname, &immutable_db_options,
|
||||
MutableDBOptions{db_options}, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller, /*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"",
|
||||
@@ -163,7 +164,8 @@ class MemTableListTest : public testing::Test {
|
||||
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
|
||||
WriteController write_controller(10000000u);
|
||||
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
VersionSet versions(dbname, &immutable_db_options,
|
||||
MutableDBOptions{db_options}, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller, /*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"",
|
||||
|
||||
@@ -303,6 +303,47 @@ TEST_F(ObsoleteFilesTest, BlobFiles) {
|
||||
ASSERT_EQ(deleted_files, expected_deleted_files);
|
||||
}
|
||||
|
||||
TEST_F(ObsoleteFilesTest, GetSortedWalFilesHangsAfterNoopPurge) {
|
||||
// This test used to trigger a hang in `DB::GetSortedWalFiles()`, where it
|
||||
// would wait for a no-op purge that did not signal the CV upon completion.
|
||||
|
||||
// Grab an iterator and flush to switch the super version. That way, when the
|
||||
// iterator is destroyed, it will go through the purge path.
|
||||
DB* db = db_; // Only using `db` makes it clear we only use DB-level APIs.
|
||||
ASSERT_OK(db->Put(WriteOptions(), "key", "value"));
|
||||
std::unique_ptr<Iterator> iter(db->NewIterator(ReadOptions()));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Sync points ensure `GetSortedWalFiles()` waits for a purge after
|
||||
// `FindObsoleteFiles()` releases the mutex but before its corresponding purge
|
||||
// completes.
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FindObsoleteFiles::PostMutexUnlock", [&](void* /* arg */) {
|
||||
TEST_SYNC_POINT(
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
|
||||
"InCallback:1");
|
||||
TEST_SYNC_POINT(
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
|
||||
"InCallback:2");
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:1",
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin"},
|
||||
{"DBImpl::GetSortedWalFilesImpl:WaitPurge",
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:2"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread get_sorted_wal_files_thread([db]() {
|
||||
TEST_SYNC_POINT(
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin");
|
||||
VectorWalPtr files;
|
||||
ASSERT_OK(db->GetSortedWalFiles(files));
|
||||
});
|
||||
iter.reset();
|
||||
get_sorted_wal_files_thread.join();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+2
-2
@@ -120,8 +120,8 @@ class Repairer {
|
||||
/*io_tracer=*/nullptr, db_session_id_)),
|
||||
wb_(db_options_.db_write_buffer_size),
|
||||
wc_(db_options_.delayed_write_rate),
|
||||
vset_(dbname_, &immutable_db_options_, file_options_,
|
||||
raw_table_cache_.get(), &wb_, &wc_,
|
||||
vset_(dbname_, &immutable_db_options_, MutableDBOptions{db_options_},
|
||||
file_options_, raw_table_cache_.get(), &wb_, &wc_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", db_session_id_, db_options.daily_offpeak_time_utc,
|
||||
/*error_handler=*/nullptr, /*read_only=*/false),
|
||||
|
||||
+7
-4
@@ -557,16 +557,19 @@ struct SubcompactionProgress {
|
||||
Slice key_slice(next_internal_key_to_compact);
|
||||
if (ParseInternalKey(key_slice, &parsed_key, false /* log_err_key */)
|
||||
.ok()) {
|
||||
oss << "user_key=\"" << parsed_key.user_key.ToString(false /* hex */)
|
||||
<< "\" (hex:" << parsed_key.user_key.ToString(true /* hex */)
|
||||
<< ")";
|
||||
oss << "user_key(hex)=" << parsed_key.user_key.ToString(true /* hex */);
|
||||
oss << ", seq=";
|
||||
if (parsed_key.sequence == kMaxSequenceNumber) {
|
||||
oss << "kMaxSequenceNumber";
|
||||
} else {
|
||||
oss << parsed_key.sequence;
|
||||
}
|
||||
oss << ", type=" << static_cast<int>(parsed_key.type);
|
||||
oss << ", type=";
|
||||
if (parsed_key.type == kValueTypeForSeek) {
|
||||
oss << "kValueTypeForSeek";
|
||||
} else {
|
||||
oss << static_cast<int>(parsed_key.type);
|
||||
}
|
||||
} else {
|
||||
oss << "raw=" << key_slice.ToString(true /* hex */);
|
||||
}
|
||||
|
||||
+290
-90
@@ -16,6 +16,7 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -980,7 +981,8 @@ class LevelIterator final : public InternalIterator {
|
||||
nullptr,
|
||||
bool allow_unprepared_value = false,
|
||||
std::unique_ptr<TruncatedRangeDelIterator>*** range_tombstone_iter_ptr_ =
|
||||
nullptr)
|
||||
nullptr,
|
||||
Statistics* db_statistics = nullptr, SystemClock* clock = nullptr)
|
||||
: table_cache_(table_cache),
|
||||
read_options_(read_options),
|
||||
file_options_(file_options),
|
||||
@@ -1005,7 +1007,9 @@ class LevelIterator final : public InternalIterator {
|
||||
allow_unprepared_value_(allow_unprepared_value),
|
||||
is_next_read_sequential_(false),
|
||||
to_return_sentinel_(false),
|
||||
scan_opts_(nullptr) {
|
||||
scan_opts_(nullptr),
|
||||
db_statistics_(db_statistics),
|
||||
clock_(clock) {
|
||||
// Empty level is not supported.
|
||||
assert(flevel_ != nullptr && flevel_->num_files > 0);
|
||||
if (range_tombstone_iter_ptr_) {
|
||||
@@ -1013,7 +1017,15 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
~LevelIterator() override { delete file_iter_.Set(nullptr); }
|
||||
~LevelIterator() override {
|
||||
delete file_iter_.Set(nullptr);
|
||||
// Clean up any prepared iterators that weren't used
|
||||
for (auto& entry : prepared_iters_) {
|
||||
delete entry.second;
|
||||
}
|
||||
prepared_iters_.clear();
|
||||
assert(prepared_iters_.size() == 0);
|
||||
}
|
||||
|
||||
// Seek to the first file with a key >= target.
|
||||
// If range_tombstone_iter_ is not nullptr, then we pretend that file
|
||||
@@ -1124,10 +1136,12 @@ class LevelIterator final : public InternalIterator {
|
||||
|
||||
void Prepare(const MultiScanArgs* so) override {
|
||||
// We assume here that scan_opts is sorted such that
|
||||
// scan_opts[0].range.start < scan_opts[1].range.start, and non overlapping
|
||||
// scan_opts[0].range.start < scan_opts[1].range.start, and non
|
||||
// overlapping
|
||||
if (so == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
scan_opts_ = so;
|
||||
|
||||
// Verify comparator is consistent
|
||||
@@ -1188,8 +1202,8 @@ class LevelIterator final : public InternalIterator {
|
||||
continue;
|
||||
}
|
||||
auto const metadata = flevel_->files[i].file_metadata;
|
||||
if (metadata->num_entries == metadata->num_range_deletions) {
|
||||
// Skip range deletion only files.
|
||||
if (metadata->FileIsStandAloneRangeTombstone()) {
|
||||
// Skip stand alone range deletion files.
|
||||
continue;
|
||||
}
|
||||
auto& args = GetMultiScanArgForFile(i);
|
||||
@@ -1197,9 +1211,35 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StopWatch timer(clock_, db_statistics_, MULTISCAN_PREPARE_ITERATORS);
|
||||
|
||||
// Propagate multiscan configs
|
||||
for (auto& file_to_arg : *file_to_scan_opts_) {
|
||||
file_to_arg.second.CopyConfigFrom(*so);
|
||||
assert(OverlapRange(*file_to_arg.second.GetScanRanges().begin(),
|
||||
file_to_arg.first) &&
|
||||
OverlapRange(*file_to_arg.second.GetScanRanges().rbegin(),
|
||||
file_to_arg.first));
|
||||
}
|
||||
|
||||
if (so->use_async_io) {
|
||||
auto before = file_index_;
|
||||
// Pre-create and prepare only relevant file iterators
|
||||
for (auto& file_to_arg : *file_to_scan_opts_) {
|
||||
size_t file_index = file_to_arg.first;
|
||||
|
||||
file_index_ = file_index;
|
||||
// Create iterator for this file
|
||||
auto iter = NewFileIterator();
|
||||
if (iter != nullptr) {
|
||||
// If we have async enabled, lets prepare all our iterators.
|
||||
iter->Prepare(&file_to_arg.second);
|
||||
// Store the prepared iterator
|
||||
prepared_iters_[file_index] = iter;
|
||||
}
|
||||
}
|
||||
file_index_ = before;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1276,7 +1316,7 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool OverlapRange(const ScanOptions& opts);
|
||||
bool OverlapRange(const ScanOptions& opts, size_t file_index);
|
||||
#endif
|
||||
|
||||
TableCache* table_cache_;
|
||||
@@ -1334,9 +1374,15 @@ class LevelIterator final : public InternalIterator {
|
||||
bool to_return_sentinel_ = false;
|
||||
const MultiScanArgs* scan_opts_ = nullptr;
|
||||
|
||||
Statistics* db_statistics_ = nullptr;
|
||||
SystemClock* clock_ = nullptr;
|
||||
|
||||
// Our stored scan_opts for each prefix
|
||||
std::unique_ptr<ScanOptionsMap> file_to_scan_opts_ = nullptr;
|
||||
|
||||
// Map to store pre-created iterators by file index
|
||||
std::unordered_map<size_t, InternalIterator*> prepared_iters_;
|
||||
|
||||
// Sets flags for if we should return the sentinel key next.
|
||||
// The condition for returning sentinel is reaching the end of current
|
||||
// file_iter_: !Valid() && status.().ok().
|
||||
@@ -1613,8 +1659,19 @@ bool LevelIterator::SkipEmptyFileForward() {
|
||||
const ScanOptions& opts =
|
||||
GetMultiScanArgForFile(file_index_).GetScanRanges().front();
|
||||
if (opts.range.start.has_value()) {
|
||||
InternalKey target(*opts.range.start.AsPtr(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
InternalKey target;
|
||||
const size_t ts_size =
|
||||
user_comparator_.user_comparator()->timestamp_size();
|
||||
if (ts_size == 0) {
|
||||
target = InternalKey(opts.range.start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
} else {
|
||||
std::string seek_key;
|
||||
AppendKeyWithMaxTimestamp(&seek_key, opts.range.start.value(),
|
||||
ts_size);
|
||||
target =
|
||||
InternalKey(seek_key, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
}
|
||||
file_iter_.Seek(target.Encode());
|
||||
}
|
||||
} else {
|
||||
@@ -1662,14 +1719,14 @@ void LevelIterator::SkipEmptyFileBackward() {
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool LevelIterator::OverlapRange(const ScanOptions& opts) {
|
||||
bool LevelIterator::OverlapRange(const ScanOptions& opts, size_t file_index) {
|
||||
return (user_comparator_.CompareWithoutTimestamp(
|
||||
opts.range.start.value(), /*a_has_ts=*/false,
|
||||
ExtractUserKey(flevel_->files[file_index_].largest_key),
|
||||
ExtractUserKey(flevel_->files[file_index].largest_key),
|
||||
/*b_has_ts=*/true) <= 0 &&
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
opts.range.limit.value(), /*a_has_ts=*/false,
|
||||
ExtractUserKey(flevel_->files[file_index_].smallest_key),
|
||||
ExtractUserKey(flevel_->files[file_index].smallest_key),
|
||||
/*b_has_ts=*/true) > 0);
|
||||
}
|
||||
#endif
|
||||
@@ -1680,15 +1737,6 @@ void LevelIterator::SetFileIterator(InternalIterator* iter) {
|
||||
}
|
||||
|
||||
InternalIterator* old_iter = file_iter_.Set(iter);
|
||||
if (iter && scan_opts_) {
|
||||
if (FileHasMultiScanArg(file_index_)) {
|
||||
const MultiScanArgs& new_opts = GetMultiScanArgForFile(file_index_);
|
||||
assert(OverlapRange(*new_opts.GetScanRanges().begin()) &&
|
||||
OverlapRange(*new_opts.GetScanRanges().rbegin()));
|
||||
file_iter_.Prepare(&new_opts);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the read pattern for PrefetchBuffer.
|
||||
if (is_next_read_sequential_) {
|
||||
file_iter_.UpdateReadaheadState(old_iter);
|
||||
@@ -1718,7 +1766,24 @@ void LevelIterator::InitFileIterator(size_t new_file_index) {
|
||||
// no need to change anything
|
||||
} else {
|
||||
file_index_ = new_file_index;
|
||||
if (!prepared_iters_.empty()) {
|
||||
auto prepared_it = prepared_iters_.find(file_index_);
|
||||
if (prepared_it != prepared_iters_.end()) {
|
||||
InternalIterator* iter = prepared_it->second;
|
||||
prepared_iters_.erase(prepared_it);
|
||||
SetFileIterator(iter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
InternalIterator* iter = NewFileIterator();
|
||||
if (FileHasMultiScanArg(file_index_)) {
|
||||
auto& args = GetMultiScanArgForFile(file_index_);
|
||||
assert(OverlapRange(*args.GetScanRanges().begin(), file_index_) &&
|
||||
OverlapRange(*args.GetScanRanges().rbegin(), file_index_));
|
||||
iter->Prepare(&args);
|
||||
}
|
||||
|
||||
SetFileIterator(iter);
|
||||
}
|
||||
}
|
||||
@@ -2024,6 +2089,79 @@ void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) {
|
||||
}
|
||||
}
|
||||
|
||||
void Version::GetColumnFamilyMetaData(
|
||||
const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* cf_meta) {
|
||||
assert(cf_meta);
|
||||
assert(cfd_);
|
||||
|
||||
cf_meta->name = cfd_->GetName();
|
||||
cf_meta->size = 0;
|
||||
cf_meta->file_count = 0;
|
||||
cf_meta->levels.clear();
|
||||
cf_meta->blob_file_size = 0;
|
||||
cf_meta->blob_file_count = 0;
|
||||
cf_meta->blob_files.clear();
|
||||
|
||||
const auto& ioptions = cfd_->ioptions();
|
||||
auto* vstorage = storage_info();
|
||||
|
||||
int first_level = (options.level >= 0) ? options.level : 0;
|
||||
int last_level =
|
||||
(options.level >= 0) ? options.level + 1 : cfd_->NumberLevels();
|
||||
|
||||
InternalKey ikey_start, ikey_end;
|
||||
const InternalKey* begin = nullptr;
|
||||
const InternalKey* end = nullptr;
|
||||
|
||||
if (options.range.start.has_value()) {
|
||||
ikey_start = InternalKey(options.range.start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
begin = &ikey_start;
|
||||
}
|
||||
|
||||
if (options.range.limit.has_value()) {
|
||||
ikey_end = InternalKey(options.range.limit.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
end = &ikey_end;
|
||||
}
|
||||
|
||||
for (int l = first_level; l < last_level; ++l) {
|
||||
uint64_t level_size = 0;
|
||||
std::vector<SstFileMetaData> files;
|
||||
std::vector<FileMetaData*> overlapping_files;
|
||||
vstorage->GetOverlappingInputs(l, begin, end, &overlapping_files);
|
||||
|
||||
for (const auto& file : overlapping_files) {
|
||||
uint32_t path_id = file->fd.GetPathId();
|
||||
const auto& file_path = (path_id < ioptions.cf_paths.size())
|
||||
? ioptions.cf_paths[path_id].path
|
||||
: ioptions.cf_paths.back().path;
|
||||
const uint64_t file_number = file->fd.GetNumber();
|
||||
files.emplace_back(
|
||||
MakeTableFileName("", file_number), file_number, file_path,
|
||||
file->fd.GetFileSize(), file->fd.smallest_seqno,
|
||||
file->fd.largest_seqno, file->smallest.user_key().ToString(),
|
||||
file->largest.user_key().ToString(),
|
||||
file->stats.num_reads_sampled.load(std::memory_order_relaxed),
|
||||
file->being_compacted, file->temperature,
|
||||
file->oldest_blob_file_number, file->TryGetOldestAncesterTime(),
|
||||
file->TryGetFileCreationTime(), file->epoch_number,
|
||||
file->file_checksum, file->file_checksum_func_name);
|
||||
files.back().num_entries = file->num_entries;
|
||||
files.back().num_deletions = file->num_deletions;
|
||||
files.back().smallest = file->smallest.Encode().ToString();
|
||||
files.back().largest = file->largest.Encode().ToString();
|
||||
level_size += file->fd.GetFileSize();
|
||||
cf_meta->file_count++;
|
||||
}
|
||||
if (!files.empty()) {
|
||||
cf_meta->levels.emplace_back(l, level_size, std::move(files));
|
||||
cf_meta->size += level_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Version::GetSstFilesSize() {
|
||||
uint64_t sst_files_size = 0;
|
||||
for (int level = 0; level < storage_info_.num_levels_; level++) {
|
||||
@@ -2108,7 +2246,7 @@ InternalIterator* Version::TEST_GetLevelIterator(
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
nullptr /* range_del_agg */, nullptr /* compaction_boundaries */,
|
||||
allow_unprepared_value, &tombstone_iter_ptr);
|
||||
allow_unprepared_value, &tombstone_iter_ptr, db_statistics_, clock_);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(level_iter);
|
||||
} else {
|
||||
@@ -2248,7 +2386,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
/*range_del_agg=*/nullptr,
|
||||
/*compaction_boundaries=*/nullptr, allow_unprepared_value,
|
||||
&tombstone_iter_ptr);
|
||||
&tombstone_iter_ptr, db_statistics_, clock_);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(level_iter);
|
||||
} else {
|
||||
@@ -2305,7 +2443,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
mutable_cf_options_, should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
&range_del_agg, nullptr, false));
|
||||
&range_del_agg, nullptr, false, nullptr, db_statistics_, clock_));
|
||||
status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key,
|
||||
iter.get(), overlap);
|
||||
}
|
||||
@@ -4487,7 +4625,8 @@ bool VersionStorageInfo::OverlapInLevel(int level,
|
||||
void VersionStorageInfo::GetOverlappingInputs(
|
||||
int level, const InternalKey* begin, const InternalKey* end,
|
||||
std::vector<FileMetaData*>* inputs, int hint_index, int* file_index,
|
||||
bool expand_range, InternalKey** next_smallest) const {
|
||||
bool expand_range, const FileMetaData* starting_l0_file,
|
||||
InternalKey** next_smallest) const {
|
||||
if (level >= num_non_empty_levels_) {
|
||||
// this level is empty, no overlapping inputs
|
||||
return;
|
||||
@@ -4520,7 +4659,19 @@ void VersionStorageInfo::GetOverlappingInputs(
|
||||
|
||||
// index stores the file index need to check.
|
||||
std::list<size_t> index;
|
||||
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
|
||||
size_t start_index = 0;
|
||||
if (starting_l0_file != nullptr) {
|
||||
uint64_t starting_file_number = starting_l0_file->fd.GetNumber();
|
||||
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
|
||||
if (level_files_brief_[level].files[i].fd.GetNumber() ==
|
||||
starting_file_number) {
|
||||
start_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(start_index < level_files_brief_[level].num_files);
|
||||
}
|
||||
for (size_t i = start_index; i < level_files_brief_[level].num_files; i++) {
|
||||
index.emplace_back(i);
|
||||
}
|
||||
|
||||
@@ -5307,6 +5458,7 @@ void AtomicGroupReadBuffer::Clear() {
|
||||
|
||||
VersionSet::VersionSet(
|
||||
const std::string& dbname, const ImmutableDBOptions* _db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& storage_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
@@ -5335,6 +5487,7 @@ VersionSet::VersionSet(
|
||||
prev_log_number_(0),
|
||||
current_version_number_(0),
|
||||
manifest_file_size_(0),
|
||||
last_compacted_manifest_file_size_(0),
|
||||
file_options_(storage_options),
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
io_tracer_(io_tracer),
|
||||
@@ -5342,7 +5495,9 @@ VersionSet::VersionSet(
|
||||
offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)),
|
||||
error_handler_(error_handler),
|
||||
unchanging_(unchanging),
|
||||
closed_(false) {}
|
||||
closed_(false) {
|
||||
UpdatedMutableDbOptions(mutable_db_options, /*mu=*/nullptr);
|
||||
}
|
||||
|
||||
Status VersionSet::Close(FSDirectory* db_dir, InstrumentedMutex* mu) {
|
||||
Status s;
|
||||
@@ -5438,11 +5593,38 @@ void VersionSet::Reset() {
|
||||
current_version_number_ = 0;
|
||||
manifest_writers_.clear();
|
||||
manifest_file_size_ = 0;
|
||||
last_compacted_manifest_file_size_ = 0;
|
||||
TuneMaxManifestFileSize();
|
||||
obsolete_files_.clear();
|
||||
obsolete_manifests_.clear();
|
||||
wals_.Reset();
|
||||
}
|
||||
|
||||
void VersionSet::UpdatedMutableDbOptions(
|
||||
const MutableDBOptions& updated_options, InstrumentedMutex* mu) {
|
||||
// Must be holding mutex if not called during initialization
|
||||
if (mu) {
|
||||
mu->AssertHeld();
|
||||
} else {
|
||||
// manifest_file_size_ must be 0 if called from the constructor
|
||||
assert(manifest_file_size_ == 0);
|
||||
}
|
||||
file_options_.writable_file_max_buffer_size =
|
||||
updated_options.writable_file_max_buffer_size;
|
||||
min_max_manifest_file_size_ = updated_options.max_manifest_file_size;
|
||||
max_manifest_space_amp_pct_ = static_cast<unsigned>(
|
||||
std::max(updated_options.max_manifest_space_amp_pct, 0));
|
||||
manifest_preallocation_size_ = updated_options.manifest_preallocation_size;
|
||||
TuneMaxManifestFileSize();
|
||||
}
|
||||
|
||||
void VersionSet::TuneMaxManifestFileSize() {
|
||||
tuned_max_manifest_file_size_ =
|
||||
std::max(min_max_manifest_file_size_,
|
||||
last_compacted_manifest_file_size_ *
|
||||
(100U + max_manifest_space_amp_pct_) / 100U);
|
||||
}
|
||||
|
||||
void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
|
||||
Version* v) {
|
||||
// compute new compaction score
|
||||
@@ -5525,8 +5707,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// the preceding version edits in the same atomic group, and update
|
||||
// their `remaining_entries_` member variable because we are NOT going
|
||||
// to write the version edits' of dropped CF to the MANIFEST. If we
|
||||
// don't update, then Recover can report corrupted atomic group because
|
||||
// the `remaining_entries_` do not match.
|
||||
// don't update, then Recover can report corrupted atomic group
|
||||
// because the `remaining_entries_` do not match.
|
||||
if (!batch_edits.empty()) {
|
||||
if (batch_edits.back()->IsInAtomicGroup() &&
|
||||
batch_edits.back()->GetRemainingEntries() > 0) {
|
||||
@@ -5686,10 +5868,11 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
uint64_t prev_manifest_file_size = manifest_file_size_;
|
||||
assert(pending_manifest_file_number_ == 0);
|
||||
if (!skip_manifest_write &&
|
||||
(!descriptor_log_ ||
|
||||
manifest_file_size_ > db_options_->max_manifest_file_size)) {
|
||||
prev_manifest_file_size >= tuned_max_manifest_file_size_)) {
|
||||
TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:BeforeNewManifest");
|
||||
new_descriptor_log = true;
|
||||
} else {
|
||||
@@ -5729,6 +5912,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
IOStatus manifest_io_status;
|
||||
manifest_io_status.PermitUncheckedError();
|
||||
std::unique_ptr<log::Writer> new_desc_log_ptr;
|
||||
// Save before releasing mu
|
||||
uint64_t manifest_preallocation_size = manifest_preallocation_size_;
|
||||
if (skip_manifest_write) {
|
||||
if (s.ok()) {
|
||||
constexpr bool update_stats = true;
|
||||
@@ -5772,16 +5957,13 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// This is fine because everything inside of this block is serialized --
|
||||
// only one thread can be here at the same time
|
||||
// create new manifest file
|
||||
ROCKS_LOG_INFO(db_options_->info_log, "Creating manifest %" PRIu64 "\n",
|
||||
pending_manifest_file_number_);
|
||||
std::string descriptor_fname =
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_);
|
||||
std::unique_ptr<FSWritableFile> descriptor_file;
|
||||
io_s = NewWritableFile(fs_.get(), descriptor_fname, &descriptor_file,
|
||||
opt_file_opts);
|
||||
if (io_s.ok()) {
|
||||
descriptor_file->SetPreallocationBlockSize(
|
||||
db_options_->manifest_preallocation_size);
|
||||
descriptor_file->SetPreallocationBlockSize(manifest_preallocation_size);
|
||||
FileTypeSet tmp_set = db_options_->checksum_handoff_file_types;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(descriptor_file), descriptor_fname, opt_file_opts, clock_,
|
||||
@@ -5831,10 +6013,12 @@ Status VersionSet::ProcessManifestWrites(
|
||||
#ifndef NDEBUG
|
||||
if (batch_edits.size() > 1 && batch_edits.size() - 1 == idx) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0",
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
|
||||
"0",
|
||||
nullptr);
|
||||
TEST_SYNC_POINT(
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1");
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
|
||||
"1");
|
||||
}
|
||||
++idx;
|
||||
#endif /* !NDEBUG */
|
||||
@@ -5871,8 +6055,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
file_options_.temperature, dir_contains_current_file);
|
||||
if (!io_s.ok()) {
|
||||
s = io_s;
|
||||
// Quarantine old manifest file in case new manifest file's CURRENT file
|
||||
// wasn't created successfully and the old manifest is needed.
|
||||
// Quarantine old manifest file in case new manifest file's CURRENT
|
||||
// file wasn't created successfully and the old manifest is needed.
|
||||
limbo_descriptor_log_file_number.push_back(manifest_file_number_);
|
||||
files_to_quarantine_if_commit_fail.push_back(
|
||||
&limbo_descriptor_log_file_number);
|
||||
@@ -5882,6 +6066,13 @@ Status VersionSet::ProcessManifestWrites(
|
||||
if (s.ok()) {
|
||||
// find offset in manifest file where this version is stored.
|
||||
new_manifest_file_size = raw_desc_log_ptr->file()->GetFileSize();
|
||||
if (new_descriptor_log) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log,
|
||||
"Created manifest %" PRIu64
|
||||
", compacted+appended from %" PRIu64 " to %" PRIu64 "\n",
|
||||
pending_manifest_file_number_, prev_manifest_file_size,
|
||||
new_manifest_file_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
|
||||
@@ -5930,6 +6121,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
descriptor_log_ = std::move(new_desc_log_ptr);
|
||||
obsolete_manifests_.emplace_back(
|
||||
DescriptorFileName("", manifest_file_number_));
|
||||
last_compacted_manifest_file_size_ = new_manifest_file_size;
|
||||
TuneMaxManifestFileSize();
|
||||
}
|
||||
|
||||
// Install the new versions
|
||||
@@ -6012,21 +6205,21 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// that renaming tmp file to CURRENT failed.
|
||||
//
|
||||
// On local POSIX-compliant FS, the CURRENT must point to the original
|
||||
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can also
|
||||
// keep it. Future recovery will ignore this MANIFEST. It's also ok for the
|
||||
// process not to crash and continue using the db. Any future LogAndApply()
|
||||
// call will switch to a new MANIFEST and update CURRENT, still ignoring
|
||||
// this one.
|
||||
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can
|
||||
// also keep it. Future recovery will ignore this MANIFEST. It's also ok
|
||||
// for the process not to crash and continue using the db. Any future
|
||||
// LogAndApply() call will switch to a new MANIFEST and update CURRENT,
|
||||
// still ignoring this one.
|
||||
//
|
||||
// On non-local FS, it is
|
||||
// possible that the rename operation succeeded on the server (remote)
|
||||
// side, but the client somehow returns a non-ok status to RocksDB. Note
|
||||
// that this does not violate atomicity. Should we delete the new MANIFEST
|
||||
// successfully, a subsequent recovery attempt will likely see the CURRENT
|
||||
// pointing to the new MANIFEST, thus fail. We will not be able to open the
|
||||
// DB again. Therefore, if manifest operations succeed, we should keep the
|
||||
// the new MANIFEST. If the process proceeds, any future LogAndApply() call
|
||||
// will switch to a new MANIFEST and update CURRENT. If user tries to
|
||||
// pointing to the new MANIFEST, thus fail. We will not be able to open
|
||||
// the DB again. Therefore, if manifest operations succeed, we should keep
|
||||
// the the new MANIFEST. If the process proceeds, any future LogAndApply()
|
||||
// call will switch to a new MANIFEST and update CURRENT. If user tries to
|
||||
// re-open the DB,
|
||||
// a) CURRENT points to the new MANIFEST, and the new MANIFEST is present.
|
||||
// b) CURRENT points to the original MANIFEST, and the original MANIFEST
|
||||
@@ -6155,9 +6348,9 @@ Status VersionSet::LogAndApply(
|
||||
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.
|
||||
// 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);
|
||||
@@ -6211,8 +6404,8 @@ void VersionSet::LogAndApplyCFHelper(VersionEdit* edit,
|
||||
assert(!edit->HasLastSequence());
|
||||
edit->SetLastSequence(*max_last_sequence);
|
||||
if (edit->IsColumnFamilyDrop()) {
|
||||
// if we drop column family, we have to make sure to save max column family,
|
||||
// so that we don't reuse existing ID
|
||||
// if we drop column family, we have to make sure to save max column
|
||||
// family, so that we don't reuse existing ID
|
||||
edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
|
||||
}
|
||||
}
|
||||
@@ -6501,7 +6694,8 @@ void VersionSet::RecoverEpochNumbers() {
|
||||
Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
const std::string& dbname,
|
||||
FileSystem* fs) {
|
||||
// Read "CURRENT" file, which contains a pointer to the current manifest file
|
||||
// Read "CURRENT" file, which contains a pointer to the current manifest
|
||||
// file
|
||||
std::string manifest_path;
|
||||
uint64_t manifest_file_number;
|
||||
Status s = GetCurrentManifestPath(dbname, fs, /*is_retry=*/false,
|
||||
@@ -6563,14 +6757,16 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
ImmutableDBOptions db_options(*options);
|
||||
ImmutableDBOptions imm_db_options(*options);
|
||||
MutableDBOptions mutable_db_options(*options);
|
||||
ColumnFamilyOptions cf_options(*options);
|
||||
std::shared_ptr<Cache> tc(NewLRUCache(options->max_open_files - 10,
|
||||
options->table_cache_numshardbits));
|
||||
WriteController wc(options->delayed_write_rate);
|
||||
WriteBufferManager wb(options->db_write_buffer_size);
|
||||
VersionSet versions(dbname, &db_options, file_options, tc.get(), &wb, &wc,
|
||||
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/,
|
||||
VersionSet versions(dbname, &imm_db_options, mutable_db_options, file_options,
|
||||
tc.get(), &wb, &wc, nullptr /*BlockCacheTracer*/,
|
||||
nullptr /*IOTracer*/,
|
||||
/*db_id*/ "",
|
||||
/*db_session_id*/ "", options->daily_offpeak_time_utc,
|
||||
/*error_handler_*/ nullptr, /*unchanging=*/false);
|
||||
@@ -6656,9 +6852,9 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
|
||||
// Get the checksum information including the checksum and checksum function
|
||||
// name of all SST and blob files in VersionSet. Store the information in
|
||||
// FileChecksumList which contains a map from file number to its checksum info.
|
||||
// If DB is not running, make sure call VersionSet::Recover() to load the file
|
||||
// metadata from Manifest to VersionSet before calling this function.
|
||||
// FileChecksumList which contains a map from file number to its checksum
|
||||
// info. If DB is not running, make sure call VersionSet::Recover() to load
|
||||
// the file metadata from Manifest to VersionSet before calling this function.
|
||||
Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
|
||||
// Clean the previously stored checksum information if any.
|
||||
Status s;
|
||||
@@ -6800,8 +6996,8 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
// WARNING: This method doesn't hold a mutex!!
|
||||
|
||||
// This is done without DB mutex lock held, but only within single-threaded
|
||||
// LogAndApply. Column family manipulations can only happen within LogAndApply
|
||||
// (the same single thread), so we're safe to iterate.
|
||||
// LogAndApply. Column family manipulations can only happen within
|
||||
// LogAndApply (the same single thread), so we're safe to iterate.
|
||||
|
||||
assert(io_s.ok());
|
||||
if (db_options_->write_dbid_to_manifest) {
|
||||
@@ -6835,9 +7031,9 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
}
|
||||
|
||||
// New manifest should rollover the WAL deletion record from previous
|
||||
// manifest. Otherwise, when an addition record of a deleted WAL gets added to
|
||||
// this new manifest later (which can happens in e.g, SyncWAL()), this new
|
||||
// manifest creates an illusion that such WAL hasn't been deleted.
|
||||
// manifest. Otherwise, when an addition record of a deleted WAL gets added
|
||||
// to this new manifest later (which can happens in e.g, SyncWAL()), this
|
||||
// new manifest creates an illusion that such WAL hasn't been deleted.
|
||||
VersionEdit wal_deletions;
|
||||
wal_deletions.DeleteWalsBefore(min_log_number_to_keep());
|
||||
std::string wal_deletions_record;
|
||||
@@ -6969,9 +7165,9 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
// TODO(aekmekji): in CompactionJob::GenSubcompactionBoundaries(), this
|
||||
// function is called repeatedly with consecutive pairs of slices. For example
|
||||
// if the slice list is [a, b, c, d] this function is called with arguments
|
||||
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible where
|
||||
// we avoid doing binary search for the keys b and c twice and instead somehow
|
||||
// maintain state of where they first appear in the files.
|
||||
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible
|
||||
// where we avoid doing binary search for the keys b and c twice and instead
|
||||
// somehow maintain state of where they first appear in the files.
|
||||
uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
|
||||
const ReadOptions& read_options,
|
||||
Version* v, const Slice& start,
|
||||
@@ -6992,19 +7188,20 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
|
||||
}
|
||||
|
||||
// Outline of the optimization that uses options.files_size_error_margin.
|
||||
// When approximating the files total size that is used to store a keys range,
|
||||
// we first sum up the sizes of the files that fully fall into the range.
|
||||
// Then we sum up the sizes of all the files that may intersect with the range
|
||||
// (this includes all files in L0 as well). Then, if total_intersecting_size
|
||||
// is smaller than total_full_size * options.files_size_error_margin - we can
|
||||
// infer that the intersecting files have a sufficiently negligible
|
||||
// contribution to the total size, and we can approximate the storage required
|
||||
// for the keys in range as just half of the intersecting_files_size.
|
||||
// E.g., if the value of files_size_error_margin is 0.1, then the error of the
|
||||
// approximation is limited to only ~10% of the total size of files that fully
|
||||
// fall into the keys range. In such case, this helps to avoid a costly
|
||||
// process of binary searching the intersecting files that is required only
|
||||
// for a more precise calculation of the total size.
|
||||
// When approximating the files total size that is used to store a keys
|
||||
// range, we first sum up the sizes of the files that fully fall into the
|
||||
// range. Then we sum up the sizes of all the files that may intersect with
|
||||
// the range (this includes all files in L0 as well). Then, if
|
||||
// total_intersecting_size is smaller than total_full_size *
|
||||
// options.files_size_error_margin - we can infer that the intersecting
|
||||
// files have a sufficiently negligible contribution to the total size, and
|
||||
// we can approximate the storage required for the keys in range as just
|
||||
// half of the intersecting_files_size. E.g., if the value of
|
||||
// files_size_error_margin is 0.1, then the error of the approximation is
|
||||
// limited to only ~10% of the total size of files that fully fall into the
|
||||
// keys range. In such case, this helps to avoid a costly process of binary
|
||||
// searching the intersecting files that is required only for a more precise
|
||||
// calculation of the total size.
|
||||
|
||||
autovector<FdWithKeyRange*, 32> first_files;
|
||||
autovector<FdWithKeyRange*, 16> last_files;
|
||||
@@ -7076,10 +7273,11 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
|
||||
total_intersecting_size += file_ptr->fd.GetFileSize();
|
||||
}
|
||||
|
||||
// Now scan all the first & last files at each level, and estimate their size.
|
||||
// If the total_intersecting_size is less than X% of the total_full_size - we
|
||||
// want to approximate the result in order to avoid the costly binary search
|
||||
// inside ApproximateSize. We use half of file size as an approximation below.
|
||||
// Now scan all the first & last files at each level, and estimate their
|
||||
// size. If the total_intersecting_size is less than X% of the
|
||||
// total_full_size - we want to approximate the result in order to avoid the
|
||||
// costly binary search inside ApproximateSize. We use half of file size as
|
||||
// an approximation below.
|
||||
|
||||
const double margin = options.files_size_error_margin;
|
||||
if (margin > 0 && total_intersecting_size <
|
||||
@@ -7347,7 +7545,8 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
/*no per level latency histogram=*/nullptr,
|
||||
TableReaderCaller::kCompaction, /*skip_filters=*/false,
|
||||
/*level=*/static_cast<int>(c->level(which)), range_del_agg,
|
||||
c->boundaries(which), false, &tombstone_iter_ptr);
|
||||
c->boundaries(which), false, &tombstone_iter_ptr,
|
||||
db_options_->statistics.get(), clock_);
|
||||
range_tombstones.emplace_back(nullptr, tombstone_iter_ptr);
|
||||
}
|
||||
}
|
||||
@@ -7622,12 +7821,13 @@ Status VersionSet::VerifyFileMetadata(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
ReactiveVersionSet::ReactiveVersionSet(
|
||||
const std::string& dbname, const ImmutableDBOptions* _db_options,
|
||||
const std::string& dbname, const ImmutableDBOptions* imm_db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& _file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: VersionSet(dbname, _db_options, _file_options, table_cache,
|
||||
write_buffer_manager, write_controller,
|
||||
: VersionSet(dbname, imm_db_options, mutable_db_options, _file_options,
|
||||
table_cache, write_buffer_manager, write_controller,
|
||||
/*block_cache_tracer=*/nullptr, io_tracer, /*db_id*/ "",
|
||||
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false) {}
|
||||
@@ -7751,8 +7951,8 @@ Status ReactiveVersionSet::MaybeSwitchManifest(
|
||||
}
|
||||
} else if (s.IsPathNotFound()) {
|
||||
// This can happen if the primary switches to a new MANIFEST after the
|
||||
// secondary reads the CURRENT file but before the secondary actually tries
|
||||
// to open the MANIFEST.
|
||||
// secondary reads the CURRENT file but before the secondary actually
|
||||
// tries to open the MANIFEST.
|
||||
s = Status::TryAgain(
|
||||
"The primary may have switched to a new MANIFEST and deleted the old "
|
||||
"one.");
|
||||
|
||||
+56
-9
@@ -268,8 +268,13 @@ class VersionStorageInfo {
|
||||
bool expand_range = true, // if set, returns files which overlap the
|
||||
// range and overlap each other. If false,
|
||||
// then just files intersecting the range
|
||||
InternalKey** next_smallest = nullptr) // if non-null, returns the
|
||||
const; // smallest key of next file not included
|
||||
const FileMetaData* starting_l0_file =
|
||||
nullptr, // If not null, restricts L0 file selection to only include
|
||||
// files at or older than starting_l0_file.
|
||||
InternalKey** next_smallest =
|
||||
nullptr // if non-null, returns the
|
||||
// smallest key of next file not included
|
||||
) const;
|
||||
void GetCleanInputsWithinInterval(
|
||||
int level, const InternalKey* begin, // nullptr means before all keys
|
||||
const InternalKey* end, // nullptr means after all keys
|
||||
@@ -286,8 +291,10 @@ class VersionStorageInfo {
|
||||
int hint_index, // index of overlap file
|
||||
int* file_index, // return index of overlap file
|
||||
bool within_interval = false, // if set, force the inputs within interval
|
||||
InternalKey** next_smallest = nullptr) // if non-null, returns the
|
||||
const; // smallest key of next file not included
|
||||
InternalKey** next_smallest =
|
||||
nullptr // if non-null, returns the
|
||||
// smallest key of next file not included
|
||||
) const;
|
||||
|
||||
// Returns true iff some file in the specified level overlaps
|
||||
// some part of [*smallest_user_key,*largest_user_key].
|
||||
@@ -1044,6 +1051,10 @@ class Version {
|
||||
|
||||
void GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta);
|
||||
|
||||
// Get column family metadata with optional filtering by key range and level.
|
||||
void GetColumnFamilyMetaData(const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* cf_meta);
|
||||
|
||||
void GetSstFilesBoundaryKeys(Slice* smallest_user_key,
|
||||
Slice* largest_user_key);
|
||||
|
||||
@@ -1186,7 +1197,9 @@ class AtomicGroupReadBuffer {
|
||||
// but false for secondary instance or writable DB).
|
||||
class VersionSet {
|
||||
public:
|
||||
VersionSet(const std::string& dbname, const ImmutableDBOptions* db_options,
|
||||
VersionSet(const std::string& dbname,
|
||||
const ImmutableDBOptions* imm_db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
@@ -1203,6 +1216,13 @@ class VersionSet {
|
||||
|
||||
virtual Status Close(FSDirectory* db_dir, InstrumentedMutex* mu);
|
||||
|
||||
// Requires: already holding DB mutex `mu`, to ensure
|
||||
// * Safely read values from `updated_options`
|
||||
// * Safely update fields on `this` (must be read elsewhere while holding mu)
|
||||
// except `mu` can be nullptr during initialization
|
||||
void UpdatedMutableDbOptions(const MutableDBOptions& updated_options,
|
||||
InstrumentedMutex* mu);
|
||||
|
||||
Status LogAndApplyToDefaultColumnFamily(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
VersionEdit* edit, InstrumentedMutex* mu,
|
||||
@@ -1548,10 +1568,6 @@ class VersionSet {
|
||||
}
|
||||
|
||||
const FileOptions& file_options() { return file_options_; }
|
||||
void ChangeFileOptions(const MutableDBOptions& new_options) {
|
||||
file_options_.writable_file_max_buffer_size =
|
||||
new_options.writable_file_max_buffer_size;
|
||||
}
|
||||
|
||||
// TODO - Consider updating together when file options change in SetDBOptions
|
||||
const OffpeakTimeOption& offpeak_time_option() {
|
||||
@@ -1590,6 +1606,16 @@ class VersionSet {
|
||||
|
||||
bool& TEST_unchanging() { return const_cast<bool&>(unchanging_); }
|
||||
|
||||
uint64_t TEST_GetMinMaxManifestFileSize() {
|
||||
return min_max_manifest_file_size_;
|
||||
}
|
||||
unsigned TEST_GetMaxManifestSpaceAmpPct() {
|
||||
return max_manifest_space_amp_pct_;
|
||||
}
|
||||
size_t TEST_GetManifestPreallocationSize() {
|
||||
return manifest_preallocation_size_;
|
||||
}
|
||||
|
||||
protected:
|
||||
struct ManifestWriter;
|
||||
|
||||
@@ -1610,6 +1636,7 @@ class VersionSet {
|
||||
}
|
||||
};
|
||||
|
||||
// Revert back to a post-construction state (keep same options/settings)
|
||||
void Reset();
|
||||
|
||||
// Returns approximated offset of a key in a file for a given version.
|
||||
@@ -1648,6 +1675,11 @@ class VersionSet {
|
||||
ColumnFamilyData* cfd, const std::string& fpath,
|
||||
int level, const FileMetaData& meta);
|
||||
|
||||
// Auto-tune next max size for the current manifest file based on its initial
|
||||
// "compacted" size and other parameters saved in this VersionSet. Must be
|
||||
// holding DB mutex if outside of DB startup.
|
||||
void TuneMaxManifestFileSize();
|
||||
|
||||
// Protected by DB mutex.
|
||||
WalSet wals_;
|
||||
|
||||
@@ -1699,6 +1731,20 @@ class VersionSet {
|
||||
// Current size of manifest file
|
||||
uint64_t manifest_file_size_;
|
||||
|
||||
// Size of the populated manifest file last time it was re-written from
|
||||
// scratch.
|
||||
uint64_t last_compacted_manifest_file_size_;
|
||||
|
||||
// Auto-tuned max allowed size for the current manifest file
|
||||
uint64_t tuned_max_manifest_file_size_;
|
||||
|
||||
// Saved copy of max_manifest_file_size in (Mutable)DBOptions
|
||||
uint64_t min_max_manifest_file_size_;
|
||||
// Saved, sanitized copy from (Mutable)DBOptions
|
||||
unsigned max_manifest_space_amp_pct_;
|
||||
// Saved copy from (Mutable)DBOptions
|
||||
size_t manifest_preallocation_size_;
|
||||
|
||||
// Obsolete files, or during DB shutdown any files not referenced by what's
|
||||
// left of the in-memory LSM state.
|
||||
std::vector<ObsoleteFileInfo> obsolete_files_;
|
||||
@@ -1751,6 +1797,7 @@ class ReactiveVersionSet : public VersionSet {
|
||||
public:
|
||||
ReactiveVersionSet(const std::string& dbname,
|
||||
const ImmutableDBOptions* _db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& _file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
|
||||
+41
-39
@@ -1159,12 +1159,12 @@ class VersionSetTestBase {
|
||||
: env_(nullptr),
|
||||
dbname_(test::PerThreadDBPath(name)),
|
||||
options_(),
|
||||
db_options_(options_),
|
||||
imm_db_options_(options_),
|
||||
cf_options_(options_),
|
||||
immutable_options_(db_options_, cf_options_),
|
||||
immutable_options_(imm_db_options_, cf_options_),
|
||||
mutable_cf_options_(cf_options_),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
write_buffer_manager_(imm_db_options_.db_write_buffer_size),
|
||||
shutting_down_(false),
|
||||
table_factory_(std::make_shared<mock::MockTableFactory>()) {
|
||||
EXPECT_OK(test::CreateEnvFromSystem(ConfigOptions(), &env_, &env_guard_));
|
||||
@@ -1178,8 +1178,8 @@ class VersionSetTestBase {
|
||||
EXPECT_OK(fs_->CreateDirIfMissing(dbname_, IOOptions(), nullptr));
|
||||
|
||||
options_.env = env_;
|
||||
db_options_.env = env_;
|
||||
db_options_.fs = fs_;
|
||||
imm_db_options_.env = env_;
|
||||
imm_db_options_.fs = fs_;
|
||||
immutable_options_.env = env_;
|
||||
immutable_options_.fs = fs_;
|
||||
immutable_options_.clock = env_->GetSystemClock().get();
|
||||
@@ -1188,16 +1188,17 @@ class VersionSetTestBase {
|
||||
mutable_cf_options_.table_factory = table_factory_;
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_, nullptr);
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
nullptr);
|
||||
imm_db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
|
||||
virtual ~VersionSetTestBase() {
|
||||
@@ -1220,7 +1221,7 @@ class VersionSetTestBase {
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
if (imm_db_options_.write_dbid_to_manifest) {
|
||||
DBOptions tmp_db_options;
|
||||
tmp_db_options.env = env_;
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
|
||||
@@ -1381,8 +1382,8 @@ class VersionSetTestBase {
|
||||
|
||||
void ReopenDB() {
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
@@ -1471,7 +1472,8 @@ class VersionSetTestBase {
|
||||
const std::string dbname_;
|
||||
EnvOptions env_options_;
|
||||
Options options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
ImmutableDBOptions imm_db_options_;
|
||||
MutableDBOptions mutable_db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
ImmutableOptions immutable_options_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
@@ -1902,8 +1904,8 @@ TEST_F(VersionSetTest, WalAddition) {
|
||||
// Recover a new VersionSet.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -1970,8 +1972,8 @@ TEST_F(VersionSetTest, WalCloseWithoutSync) {
|
||||
// Recover a new VersionSet.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2024,8 +2026,8 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
// Recover a new VersionSet, only the non-closed WAL should show up.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2063,8 +2065,8 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
// Recover from the new MANIFEST, only the non-closed WAL should show up.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2184,8 +2186,8 @@ TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) {
|
||||
// Recover a new VersionSet, WAL0 is deleted, WAL1 is not.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2221,8 +2223,8 @@ TEST_F(VersionSetTest, DeleteAllWals) {
|
||||
// Recover a new VersionSet, all WALs are deleted.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2264,8 +2266,8 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
|
||||
// kept.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2444,8 +2446,8 @@ class VersionSetWithTimestampTest : public VersionSetTest {
|
||||
|
||||
void VerifyFullHistoryTsLow(uint64_t expected_ts_low) {
|
||||
std::unique_ptr<VersionSet> vset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -3500,7 +3502,7 @@ class VersionSetTestEmptyDb
|
||||
std::unique_ptr<log::Writer>* log_writer) override {
|
||||
assert(nullptr != log_writer);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
if (imm_db_options_.write_dbid_to_manifest) {
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_,
|
||||
Temperature::kUnknown));
|
||||
DBOptions tmp_db_options;
|
||||
@@ -3532,7 +3534,7 @@ class VersionSetTestEmptyDb
|
||||
const std::string VersionSetTestEmptyDb::kUnknownColumnFamilyName = "unknown";
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
CreateCurrentFile();
|
||||
@@ -3564,7 +3566,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Only a subset of column families in the MANIFEST.
|
||||
VersionEdit new_cf1;
|
||||
@@ -3605,7 +3607,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
@@ -3651,7 +3653,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
@@ -3708,7 +3710,7 @@ TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
@@ -3828,7 +3830,7 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
ASSERT_OK(s);
|
||||
log_writer->reset(new log::Writer(std::move(file_writer), 0, false));
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
if (imm_db_options_.write_dbid_to_manifest) {
|
||||
DBOptions tmp_db_options;
|
||||
tmp_db_options.env = env_;
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
|
||||
@@ -4088,7 +4090,7 @@ TEST_F(VersionSetTestMissingFiles, NoFileMissing) {
|
||||
}
|
||||
|
||||
TEST_F(VersionSetTestMissingFiles, MinLogNumberToKeep2PC) {
|
||||
db_options_.allow_2pc = true;
|
||||
imm_db_options_.allow_2pc = true;
|
||||
NewDB();
|
||||
|
||||
SstInfo sst(100, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// 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).
|
||||
@@ -23,7 +23,8 @@ class OfflineManifestWriter {
|
||||
immutable_db_options_(WithDbPath(options, db_path)),
|
||||
tc_(NewLRUCache(1 << 20 /* capacity */,
|
||||
options.table_cache_numshardbits)),
|
||||
versions_(db_path, &immutable_db_options_, sopt_, tc_.get(), &wb_, &wc_,
|
||||
versions_(db_path, &immutable_db_options_, MutableDBOptions{options},
|
||||
sopt_, tc_.get(), &wb_, &wc_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"",
|
||||
options.daily_offpeak_time_utc,
|
||||
|
||||
+7
-1
@@ -192,7 +192,13 @@ void WalManager::PurgeObsoleteWALFiles() {
|
||||
s.ToString().c_str());
|
||||
continue;
|
||||
}
|
||||
if (now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
|
||||
|
||||
// Avoid expression `now_seconds - file_m_time` when
|
||||
// `file_m_time > now_seconds` to prevent unsigned underflow in case
|
||||
// system clock goes backwards. Both timestamps are based on wall clock
|
||||
// time, which is not guaranteed to be monotonic.
|
||||
if (file_m_time <= now_seconds &&
|
||||
now_seconds - file_m_time > db_options_.WAL_ttl_seconds) {
|
||||
s = DeleteDBFile(&db_options_, file_path, archival_dir, false,
|
||||
/*force_fg=*/!wal_in_db_path_);
|
||||
if (!s.ok()) {
|
||||
|
||||
+48
-10
@@ -19,6 +19,7 @@
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -39,7 +40,7 @@ class WalManagerTest : public testing::Test {
|
||||
EXPECT_OK(DestroyDB(dbname_, Options()));
|
||||
}
|
||||
|
||||
void Init() {
|
||||
void Init(SystemClock* clock_override) {
|
||||
ASSERT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
ASSERT_OK(env_->CreateDirIfMissing(ArchivalDirectory(dbname_)));
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
@@ -47,11 +48,15 @@ class WalManagerTest : public testing::Test {
|
||||
db_options_.wal_dir = dbname_;
|
||||
db_options_.env = env_.get();
|
||||
db_options_.fs = env_->GetFileSystem();
|
||||
db_options_.clock = env_->GetSystemClock().get();
|
||||
if (clock_override == nullptr) {
|
||||
db_options_.clock = env_->GetSystemClock().get();
|
||||
} else {
|
||||
db_options_.clock = clock_override;
|
||||
}
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &db_options_, MutableDBOptions{}, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
@@ -124,7 +129,7 @@ class WalManagerTest : public testing::Test {
|
||||
};
|
||||
|
||||
TEST_F(WalManagerTest, ReadFirstRecordCache) {
|
||||
Init();
|
||||
Init(nullptr /* clock_override */);
|
||||
std::string path = dbname_ + "/000001.log";
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(env_->GetFileSystem()->NewWritableFile(path, FileOptions(), &file,
|
||||
@@ -221,7 +226,7 @@ int CountRecords(TransactionLogIterator* iter) {
|
||||
TEST_F(WalManagerTest, WALArchivalSizeLimit) {
|
||||
db_options_.WAL_ttl_seconds = 0;
|
||||
db_options_.WAL_size_limit_MB = 1000;
|
||||
Init();
|
||||
Init(nullptr /* clock_override */);
|
||||
|
||||
// TEST : Create WalManager with huge size limit and no ttl.
|
||||
// Create some archived files and call PurgeObsoleteWALFiles().
|
||||
@@ -258,7 +263,7 @@ TEST_F(WalManagerTest, WALArchivalSizeLimit) {
|
||||
|
||||
TEST_F(WalManagerTest, WALArchivalTtl) {
|
||||
db_options_.WAL_ttl_seconds = 1000;
|
||||
Init();
|
||||
Init(nullptr /* clock_override */);
|
||||
|
||||
// TEST : Create WalManager with a ttl and no size limit.
|
||||
// Create some archived log files and call PurgeObsoleteWALFiles().
|
||||
@@ -282,8 +287,41 @@ TEST_F(WalManagerTest, WALArchivalTtl) {
|
||||
ASSERT_TRUE(log_files.empty());
|
||||
}
|
||||
|
||||
TEST_F(WalManagerTest, WALArchivalTtlClockGoesBackwards) {
|
||||
// This test used to trigger an unsigned underflow bug, where WAL files were
|
||||
// incorrectly deleted when the system time moved backwards between writing
|
||||
// to a WAL and running `WalManager::PurgeObsoleteWALFiles()`.
|
||||
constexpr int kNumLogs = 5;
|
||||
constexpr int kEntriesPerLog = 100;
|
||||
|
||||
db_options_.WAL_ttl_seconds = 86400; // One day
|
||||
|
||||
// Configure mock clock to lag one second behind system time. That way, the
|
||||
// WAL file's mtime will appear to be in the future when
|
||||
// `WalManager::PurgeObsoleteWALFiles()` runs.
|
||||
int64_t now_seconds;
|
||||
ASSERT_OK(env_->GetSystemClock()->GetCurrentTime(&now_seconds));
|
||||
auto mock_clock = std::make_shared<MockSystemClock>(env_->GetSystemClock());
|
||||
mock_clock->SetCurrentTime(static_cast<uint64_t>(now_seconds - 1));
|
||||
db_options_.clock = mock_clock.get();
|
||||
|
||||
Init(mock_clock.get() /* clock */);
|
||||
|
||||
CreateArchiveLogs(kNumLogs, kEntriesPerLog);
|
||||
|
||||
const std::string archive_dir = ArchivalDirectory(dbname_);
|
||||
ASSERT_EQ(kNumLogs,
|
||||
ListSpecificFiles(env_.get(), archive_dir, kWalFile).size());
|
||||
|
||||
wal_manager_->PurgeObsoleteWALFiles();
|
||||
|
||||
// All files must still be present because TTL has not elapsed.
|
||||
ASSERT_EQ(kNumLogs,
|
||||
ListSpecificFiles(env_.get(), archive_dir, kWalFile).size());
|
||||
}
|
||||
|
||||
TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) {
|
||||
Init();
|
||||
Init(nullptr /* clock_override */);
|
||||
RollTheLog(false);
|
||||
Put("key1", std::string(1024, 'a'));
|
||||
// Create a zero record WAL file.
|
||||
@@ -297,7 +335,7 @@ TEST_F(WalManagerTest, TransactionLogIteratorMoveOverZeroFiles) {
|
||||
}
|
||||
|
||||
TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
|
||||
Init();
|
||||
Init(nullptr /* clock_override */);
|
||||
RollTheLog(false);
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
// Check that an empty iterator is returned
|
||||
@@ -305,7 +343,7 @@ TEST_F(WalManagerTest, TransactionLogIteratorJustEmptyFile) {
|
||||
}
|
||||
|
||||
TEST_F(WalManagerTest, TransactionLogIteratorNewFileWhileScanning) {
|
||||
Init();
|
||||
Init(nullptr /* clock_override */);
|
||||
CreateArchiveLogs(2, 100);
|
||||
auto iter = OpenTransactionLogIter(0);
|
||||
CreateArchiveLogs(1, 100);
|
||||
|
||||
@@ -248,6 +248,7 @@ DECLARE_string(fs_uri);
|
||||
DECLARE_uint64(ops_per_thread);
|
||||
DECLARE_uint64(log2_keys_per_lock);
|
||||
DECLARE_uint64(max_manifest_file_size);
|
||||
DECLARE_int32(max_manifest_space_amp_pct);
|
||||
DECLARE_bool(in_place_update);
|
||||
DECLARE_string(memtablerep);
|
||||
DECLARE_int32(prefix_size);
|
||||
|
||||
@@ -978,7 +978,11 @@ DEFINE_uint64(log2_keys_per_lock, 2, "Log2 of number of keys per lock");
|
||||
static const bool FLAGS_log2_keys_per_lock_dummy __attribute__((__unused__)) =
|
||||
RegisterFlagValidator(&FLAGS_log2_keys_per_lock, &ValidateUint32Range);
|
||||
|
||||
DEFINE_uint64(max_manifest_file_size, 16384, "Maximum size of a MANIFEST file");
|
||||
DEFINE_uint64(max_manifest_file_size, 16384,
|
||||
"Maximum size of a MANIFEST file (without auto-tuning)");
|
||||
|
||||
DEFINE_int32(max_manifest_space_amp_pct, 500,
|
||||
"Max manifest space amp percentage for auto-tuning");
|
||||
|
||||
DEFINE_bool(in_place_update, false, "On true, does inplace update in memtable");
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "db_stress_tool/db_stress_filters.h"
|
||||
#include "db_stress_tool/db_stress_table_properties_collector.h"
|
||||
#include "db_stress_tool/db_stress_wide_merge_operator.h"
|
||||
#include "file/file_util.h"
|
||||
#include "options/options_parser.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
@@ -194,10 +195,10 @@ std::shared_ptr<Cache> StressTest::NewCache(size_t capacity,
|
||||
exit(1);
|
||||
} else if (EndsWith(cache_type, "hyper_clock_cache")) {
|
||||
size_t estimated_entry_charge;
|
||||
if (cache_type == "fixed_hyper_clock_cache" ||
|
||||
cache_type == "hyper_clock_cache") {
|
||||
if (cache_type == "fixed_hyper_clock_cache") {
|
||||
estimated_entry_charge = FLAGS_block_size;
|
||||
} else if (cache_type == "auto_hyper_clock_cache") {
|
||||
} else if (cache_type == "auto_hyper_clock_cache" ||
|
||||
cache_type == "hyper_clock_cache") {
|
||||
estimated_entry_charge = 0;
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.");
|
||||
@@ -349,7 +350,6 @@ bool StressTest::BuildOptionsTable() {
|
||||
"1",
|
||||
"2",
|
||||
}},
|
||||
{"max_sequential_skip_in_iterations", {"4", "8", "12"}},
|
||||
{"block_based_table_factory",
|
||||
{
|
||||
keepRibbonFilterPolicyOnly ? "{filter_policy=ribbonfilter:2.35}"
|
||||
@@ -362,6 +362,13 @@ bool StressTest::BuildOptionsTable() {
|
||||
std::to_string(FLAGS_block_size + (FLAGS_seed & 0xFFFU)) + "}",
|
||||
}},
|
||||
};
|
||||
if (FLAGS_use_multiscan == 0) {
|
||||
// TODO: this can fail MultiScan when consecutive data blocks share the
|
||||
// same user at boundary. MultiScan uses user key to locate the block to
|
||||
// reach which can move the scan earlier than its current block.
|
||||
options_tbl.emplace("max_sequential_skip_in_iterations",
|
||||
std::vector<std::string>{"4", "8", "12"});
|
||||
}
|
||||
if (FLAGS_compaction_style == kCompactionStyleUniversal &&
|
||||
FLAGS_universal_max_read_amp > 0) {
|
||||
// level0_file_num_compaction_trigger needs to be at most max_read_amp
|
||||
@@ -1695,7 +1702,10 @@ Status StressTest::TestMultiScan(ThreadState* thread,
|
||||
std::vector<std::string> end_key_strs;
|
||||
// TODO support reverse BytewiseComparator in the stress test
|
||||
MultiScanArgs scan_opts(options_.comparator);
|
||||
scan_opts.use_async_io = FLAGS_multiscan_use_async_io;
|
||||
scan_opts.use_async_io =
|
||||
FLAGS_multiscan_use_async_io &&
|
||||
CheckFSFeatureSupport(options_.env->GetFileSystem().get(),
|
||||
FSSupportedOps::kAsyncIO);
|
||||
start_key_strs.reserve(num_scans);
|
||||
end_key_strs.reserve(num_scans);
|
||||
|
||||
@@ -4420,6 +4430,7 @@ void InitializeOptionsFromFlags(
|
||||
options.compression_opts.checksum = true;
|
||||
}
|
||||
options.max_manifest_file_size = FLAGS_max_manifest_file_size;
|
||||
options.max_manifest_space_amp_pct = FLAGS_max_manifest_space_amp_pct;
|
||||
options.max_subcompactions = static_cast<uint32_t>(FLAGS_subcompactions);
|
||||
options.allow_concurrent_memtable_write =
|
||||
FLAGS_allow_concurrent_memtable_write;
|
||||
|
||||
Vendored
+1
@@ -1406,6 +1406,7 @@ IOStatus PosixWritableFile::Truncate(uint64_t size, const IOOptions& /*opts*/,
|
||||
filename_, errno);
|
||||
} else {
|
||||
filesize_ = size;
|
||||
lseek(fd_, filesize_, SEEK_SET);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Vendored
+43
@@ -4,6 +4,7 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/random.h"
|
||||
|
||||
#ifdef ROCKSDB_LIB_IO_POSIX
|
||||
#include "env/io_posix.h"
|
||||
@@ -131,6 +132,48 @@ TEST_F(LogicalBlockSizeCacheTest, Ref) {
|
||||
}
|
||||
#endif
|
||||
|
||||
class PosixWritableFileTest : public testing::Test {};
|
||||
|
||||
TEST_F(PosixWritableFileTest, SeekAfterTruncate) {
|
||||
std::shared_ptr<FileSystem> fs = FileSystem::Default();
|
||||
std::string path =
|
||||
test::PerThreadDBPath("PosixWritableFileTest_SeekAfterTruncate");
|
||||
Random rnd(300);
|
||||
std::unique_ptr<FSWritableFile> wfile;
|
||||
|
||||
ASSERT_OK(fs->NewWritableFile(path, FileOptions(), &wfile, nullptr));
|
||||
ASSERT_OK(wfile->Append(rnd.RandomString(16384), IOOptions(), nullptr));
|
||||
ASSERT_OK(wfile->Truncate(4096, IOOptions(), nullptr));
|
||||
ASSERT_OK(wfile->Append(rnd.RandomString(4096), IOOptions(), nullptr));
|
||||
ASSERT_OK(wfile->Close(IOOptions(), nullptr));
|
||||
wfile.reset();
|
||||
|
||||
uint64_t size = 0;
|
||||
ASSERT_OK(fs->GetFileSize(path, IOOptions(), &size, nullptr));
|
||||
ASSERT_EQ(size, 8192);
|
||||
ASSERT_OK(fs->DeleteFile(path, IOOptions(), nullptr));
|
||||
}
|
||||
|
||||
TEST_F(PosixWritableFileTest, SeekAfterExtend) {
|
||||
std::shared_ptr<FileSystem> fs = FileSystem::Default();
|
||||
std::string path =
|
||||
test::PerThreadDBPath("PosixWritableFileTest_SeekAfterTruncate");
|
||||
Random rnd(300);
|
||||
std::unique_ptr<FSWritableFile> wfile;
|
||||
|
||||
ASSERT_OK(fs->NewWritableFile(path, FileOptions(), &wfile, nullptr));
|
||||
ASSERT_OK(wfile->Append(rnd.RandomString(4096), IOOptions(), nullptr));
|
||||
ASSERT_OK(wfile->Truncate(8192, IOOptions(), nullptr));
|
||||
ASSERT_OK(wfile->Append(rnd.RandomString(8192), IOOptions(), nullptr));
|
||||
ASSERT_OK(wfile->Close(IOOptions(), nullptr));
|
||||
wfile.reset();
|
||||
|
||||
uint64_t size = 0;
|
||||
ASSERT_OK(fs->GetFileSize(path, IOOptions(), &size, nullptr));
|
||||
ASSERT_EQ(size, 16384);
|
||||
ASSERT_OK(fs->DeleteFile(path, IOOptions(), nullptr));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif
|
||||
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
# This file contains the vast majority of folly-related build configuration
|
||||
# for the checkout_folly and build_folly targets, so that this file can be
|
||||
# hashed for purposes of caching folly builds and not hitting that cache when
|
||||
# something here changes.
|
||||
|
||||
# This provides a Makefile simulation of a Meta-internal folly integration.
|
||||
# It is not validated for general use.
|
||||
#
|
||||
# USE_FOLLY links the build targets with libfolly.a. The latter could be
|
||||
# built using 'make build_folly', or built externally and specified in
|
||||
# the CXXFLAGS and EXTRA_LDFLAGS env variables. The build_detect_platform
|
||||
# script tries to detect if an external folly dependency has been specified.
|
||||
# If not, it exports FOLLY_PATH to the path of the installed Folly and
|
||||
# dependency libraries.
|
||||
#
|
||||
# USE_FOLLY_LITE cherry picks source files from Folly to include in the
|
||||
# RocksDB library. Its faster and has fewer dependencies on 3rd party
|
||||
# libraries, but with limited functionality. For example, coroutine
|
||||
# functionality is not available.
|
||||
ifeq ($(USE_FOLLY),1)
|
||||
ifeq ($(USE_FOLLY_LITE),1)
|
||||
$(error Please specify only one of USE_FOLLY and USE_FOLLY_LITE)
|
||||
endif
|
||||
ifneq ($(strip $(FOLLY_PATH)),)
|
||||
BOOST_PATH = $(shell (ls -d $(FOLLY_PATH)/../boost*))
|
||||
DBL_CONV_PATH = $(shell (ls -d $(FOLLY_PATH)/../double-conversion*))
|
||||
GFLAGS_PATH = $(shell (ls -d $(FOLLY_PATH)/../gflags*))
|
||||
GLOG_PATH = $(shell (ls -d $(FOLLY_PATH)/../glog*))
|
||||
LIBEVENT_PATH = $(shell (ls -d $(FOLLY_PATH)/../libevent*))
|
||||
XZ_PATH = $(shell (ls -d $(FOLLY_PATH)/../xz*))
|
||||
LIBSODIUM_PATH = $(shell (ls -d $(FOLLY_PATH)/../libsodium*))
|
||||
FMT_PATH = $(shell (ls -d $(FOLLY_PATH)/../fmt*))
|
||||
|
||||
# For some reason, glog and fmt libraries are under either lib or lib64
|
||||
GLOG_LIB_PATH = $(shell (ls -d $(GLOG_PATH)/lib*))
|
||||
FMT_LIB_PATH = $(shell (ls -d $(FMT_PATH)/lib*))
|
||||
|
||||
# AIX: pre-defined system headers are surrounded by an extern "C" block
|
||||
ifeq ($(PLATFORM), OS_AIX)
|
||||
PLATFORM_CCFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
|
||||
PLATFORM_CXXFLAGS += -I$(BOOST_PATH)/include -I$(DBL_CONV_PATH)/include -I$(GLOG_PATH)/include -I$(LIBEVENT_PATH)/include -I$(XZ_PATH)/include -I$(LIBSODIUM_PATH)/include -I$(FOLLY_PATH)/include -I$(FMT_PATH)/include
|
||||
else
|
||||
PLATFORM_CCFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
|
||||
PLATFORM_CXXFLAGS += -isystem $(BOOST_PATH)/include -isystem $(DBL_CONV_PATH)/include -isystem $(GLOG_PATH)/include -isystem $(LIBEVENT_PATH)/include -isystem $(XZ_PATH)/include -isystem $(LIBSODIUM_PATH)/include -isystem $(FOLLY_PATH)/include -isystem $(FMT_PATH)/include
|
||||
endif
|
||||
|
||||
# Add -ldl at the end as gcc resolves a symbol in a library by searching only in libraries specified later
|
||||
# in the command line
|
||||
|
||||
PLATFORM_LDFLAGS += $(FOLLY_PATH)/lib/libfolly.a $(BOOST_PATH)/lib/libboost_context.a $(BOOST_PATH)/lib/libboost_filesystem.a $(BOOST_PATH)/lib/libboost_atomic.a $(BOOST_PATH)/lib/libboost_program_options.a $(BOOST_PATH)/lib/libboost_regex.a $(BOOST_PATH)/lib/libboost_system.a $(BOOST_PATH)/lib/libboost_thread.a $(DBL_CONV_PATH)/lib/libdouble-conversion.a $(LIBEVENT_PATH)/lib/libevent-2.1.so $(LIBSODIUM_PATH)/lib/libsodium.a -ldl
|
||||
ifneq ($(DEBUG_LEVEL),0)
|
||||
PLATFORM_LDFLAGS += $(FMT_LIB_PATH)/libfmtd.a $(GLOG_LIB_PATH)/libglogd.so $(GFLAGS_PATH)/lib/libgflags_debug.so.2.2
|
||||
else
|
||||
PLATFORM_LDFLAGS += $(FMT_LIB_PATH)/libfmt.a $(GLOG_LIB_PATH)/libglog.so $(GFLAGS_PATH)/lib/libgflags.so.2.2
|
||||
endif
|
||||
PLATFORM_LDFLAGS += -Wl,-rpath=$(LIBEVENT_PATH)/lib -Wl,-rpath=$(GLOG_LIB_PATH) -Wl,-rpath=$(GFLAGS_PATH)/lib
|
||||
endif
|
||||
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
endif
|
||||
|
||||
ifeq ($(USE_FOLLY_LITE),1)
|
||||
# Path to the Folly source code and include files
|
||||
FOLLY_DIR = ./third-party/folly
|
||||
ifneq ($(strip $(BOOST_SOURCE_PATH)),)
|
||||
BOOST_INCLUDE = $(shell (ls -d $(BOOST_SOURCE_PATH)/boost*/))
|
||||
# AIX: pre-defined system headers are surrounded by an extern "C" block
|
||||
ifeq ($(PLATFORM), OS_AIX)
|
||||
PLATFORM_CCFLAGS += -I$(BOOST_INCLUDE)
|
||||
PLATFORM_CXXFLAGS += -I$(BOOST_INCLUDE)
|
||||
else
|
||||
PLATFORM_CCFLAGS += -isystem $(BOOST_INCLUDE)
|
||||
PLATFORM_CXXFLAGS += -isystem $(BOOST_INCLUDE)
|
||||
endif
|
||||
endif # BOOST_SOURCE_PATH
|
||||
ifneq ($(strip $(FMT_SOURCE_PATH)),)
|
||||
FMT_INCLUDE = $(shell (ls -d $(FMT_SOURCE_PATH)/fmt*/include/))
|
||||
# AIX: pre-defined system headers are surrounded by an extern "C" block
|
||||
ifeq ($(PLATFORM), OS_AIX)
|
||||
PLATFORM_CCFLAGS += -I$(FMT_INCLUDE)
|
||||
PLATFORM_CXXFLAGS += -I$(FMT_INCLUDE)
|
||||
else
|
||||
PLATFORM_CCFLAGS += -isystem $(FMT_INCLUDE)
|
||||
PLATFORM_CXXFLAGS += -isystem $(FMT_INCLUDE)
|
||||
endif
|
||||
endif # FMT_SOURCE_PATH
|
||||
# AIX: pre-defined system headers are surrounded by an extern "C" block
|
||||
ifeq ($(PLATFORM), OS_AIX)
|
||||
PLATFORM_CCFLAGS += -I$(FOLLY_DIR)
|
||||
PLATFORM_CXXFLAGS += -I$(FOLLY_DIR)
|
||||
else
|
||||
PLATFORM_CCFLAGS += -isystem $(FOLLY_DIR)
|
||||
PLATFORM_CXXFLAGS += -isystem $(FOLLY_DIR)
|
||||
endif
|
||||
PLATFORM_CCFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
PLATFORM_CXXFLAGS += -DUSE_FOLLY -DFOLLY_NO_CONFIG
|
||||
# TODO: fix linking with fbcode compiler config
|
||||
PLATFORM_LDFLAGS += -lglog
|
||||
endif
|
||||
|
||||
FOLLY_COMMIT_HASH = abe68f7e917e8b7a0ee2fe066c972dc98fd35aa1
|
||||
|
||||
# For public CI runs, checkout folly in a way that can build with RocksDB.
|
||||
# This is mostly intended as a test-only simulation of Meta-internal folly
|
||||
# integration.
|
||||
checkout_folly:
|
||||
if [ -e third-party/folly ]; then \
|
||||
cd third-party/folly && ${GIT_COMMAND} fetch origin; \
|
||||
else \
|
||||
cd third-party && ${GIT_COMMAND} clone https://github.com/facebook/folly.git; \
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard $(FOLLY_COMMIT_HASH)
|
||||
@# Apparently missing include
|
||||
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
|
||||
@# const mismatch
|
||||
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
|
||||
@# Use gnu.org mirrors to improve download speed (ftp.gnu.org is often super slow)
|
||||
cd third-party/folly && perl -pi -e 's/ftp.gnu.org/ftpmirror.gnu.org/' `git grep -l ftp.gnu.org` README.md
|
||||
@# NOTE: boost and fmt source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on those headers
|
||||
cd third-party/folly && GETDEPS_USE_WGET=1 $(PYTHON) build/fbcode_builder/getdeps.py fetch boost && GETDEPS_USE_WGET=1 $(PYTHON) build/fbcode_builder/getdeps.py fetch fmt
|
||||
|
||||
CXX_M_FLAGS = $(filter -m%, $(CXXFLAGS))
|
||||
|
||||
FOLLY_BUILD_FLAGS = --no-tests
|
||||
# NOTE: To avoid ODR violations, we must build folly in debug mode iff
|
||||
# building RocksDB in debug mode.
|
||||
ifneq ($(DEBUG_LEVEL),0)
|
||||
FOLLY_BUILD_FLAGS += --build-type Debug
|
||||
endif
|
||||
|
||||
build_folly:
|
||||
FOLLY_INST_PATH=`cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
|
||||
if [ "$$FOLLY_INST_PATH" ]; then \
|
||||
rm -rf $${FOLLY_INST_PATH}/../../*; \
|
||||
else \
|
||||
echo "Please run checkout_folly first"; \
|
||||
false; \
|
||||
fi
|
||||
cd third-party/folly && \
|
||||
CXXFLAGS=" $(CXX_M_FLAGS) -DHAVE_CXX11_ATOMIC " GETDEPS_USE_WGET=1 $(PYTHON) build/fbcode_builder/getdeps.py build $(FOLLY_BUILD_FLAGS)
|
||||
@# In the folly build, glog and gflags are only built as dynamic libraries,
|
||||
@# not static. This patchelf command is needed to reliably have the glog
|
||||
@# library find its dependency gflags, because apparently the rpath of the
|
||||
@# final binary is not used in resolving that transitive dependency.
|
||||
FOLLY_INST_PATH=`cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
|
||||
cd "$$FOLLY_INST_PATH" && patchelf --add-rpath $$PWD/../gflags-*/lib ../glog-*/lib*/libglog*.so.*.*.*
|
||||
@@ -90,6 +90,10 @@ class Compressor {
|
||||
return CompressionType::kDisableCompressionOption;
|
||||
}
|
||||
|
||||
// Return a distinct but functionally equivalent Compressor. This is often
|
||||
// needed to implement MaybeCloneSpecialized() in wrapper compressors.
|
||||
virtual std::unique_ptr<Compressor> Clone() const = 0;
|
||||
|
||||
// Utility struct for providing sample data for the compression dictionary.
|
||||
// Potentially extensible by callers of Compressor (but not recommended)
|
||||
struct DictSampleArgs {
|
||||
@@ -121,7 +125,7 @@ class Compressor {
|
||||
// dictionary associated with a returned compressor must be read from
|
||||
// GetSerializedDict().
|
||||
virtual std::unique_ptr<Compressor> MaybeCloneSpecialized(
|
||||
CacheEntryRole block_type, DictSampleArgs&& dict_samples) {
|
||||
CacheEntryRole block_type, DictSampleArgs&& dict_samples) const {
|
||||
// Default implementation: no specialization
|
||||
(void)block_type;
|
||||
(void)dict_samples;
|
||||
@@ -131,6 +135,18 @@ class Compressor {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// A convenience function when a clone is needed and may or may not be
|
||||
// specialized.
|
||||
std::unique_ptr<Compressor> CloneMaybeSpecialized(
|
||||
CacheEntryRole block_type, DictSampleArgs&& dict_samples) const {
|
||||
auto clone = MaybeCloneSpecialized(block_type, std::move(dict_samples));
|
||||
if (clone == nullptr) {
|
||||
clone = Clone();
|
||||
assert(clone != nullptr);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
// A WorkingArea is an optional structure (both for callers and
|
||||
// implementations) that can enable optimizing repeated compressions by
|
||||
// reusing working space or thread-local tracking of statistics or trends.
|
||||
@@ -473,9 +489,19 @@ class CompressorWrapper : public Compressor {
|
||||
return wrapped_->GetPreferredCompressionType();
|
||||
}
|
||||
|
||||
// NOTE: Clone() not implemented here because it needs to be in the derived
|
||||
// class
|
||||
|
||||
// NOTE: MaybeCloneSpecialized() is only implemented here for convenience
|
||||
// when the wrapped Compressor uses the default implementation of
|
||||
// MaybeCloneSpecialized(). This needs to be overridden if not.
|
||||
std::unique_ptr<Compressor> MaybeCloneSpecialized(
|
||||
CacheEntryRole block_type, DictSampleArgs&& dict_samples) override {
|
||||
return wrapped_->MaybeCloneSpecialized(block_type, std::move(dict_samples));
|
||||
CacheEntryRole block_type, DictSampleArgs&& dict_samples) const override {
|
||||
auto clone =
|
||||
wrapped_->MaybeCloneSpecialized(block_type, std::move(dict_samples));
|
||||
// Assert default no-op MaybeCloneSpecialized()
|
||||
assert(clone == nullptr);
|
||||
return clone;
|
||||
}
|
||||
|
||||
ManagedWorkingArea ObtainWorkingArea() override {
|
||||
|
||||
@@ -156,6 +156,61 @@ enum class PrepopulateBlobCache : uint8_t {
|
||||
kFlushOnly = 0x1, // Prepopulate blobs during flush only
|
||||
};
|
||||
|
||||
// Bitmask enum for verify output flags during compaction.
|
||||
// This allows fine-grained control over what verification is performed
|
||||
// on compaction output files and when it's enabled.
|
||||
enum class VerifyOutputFlags : uint32_t {
|
||||
kVerifyNone = 0x0, // No verification
|
||||
|
||||
// First set of bits: type of verifications
|
||||
kVerifyBlockChecksum = 1 << 0, // Verify block checksums
|
||||
kVerifyIteration = 1 << 1, // Verify iteration and full key/value hash
|
||||
// by comparing the one inserted into a
|
||||
// file, and what is read back.
|
||||
|
||||
// TODO - Implement
|
||||
// kVerifyFileChecksum = 1 << 2, // Verify file-level checksum
|
||||
|
||||
// Second set of bits: when to enable verification
|
||||
kEnableForLocalCompaction = 1 << 10, // Enable for local compaction
|
||||
kEnableForRemoteCompaction = 1 << 11, // Enable for remote compaction
|
||||
|
||||
// TODO - Implement
|
||||
// kEnableForFlush = 1 << 12, // Enable for flush
|
||||
|
||||
kVerifyAll = 0xFFFFFFFF,
|
||||
};
|
||||
|
||||
inline VerifyOutputFlags operator|(VerifyOutputFlags lhs,
|
||||
VerifyOutputFlags rhs) {
|
||||
using T = std::underlying_type_t<VerifyOutputFlags>;
|
||||
return static_cast<VerifyOutputFlags>(static_cast<T>(lhs) |
|
||||
static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
inline VerifyOutputFlags& operator|=(VerifyOutputFlags& lhs,
|
||||
VerifyOutputFlags rhs) {
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline VerifyOutputFlags operator&(VerifyOutputFlags lhs,
|
||||
VerifyOutputFlags rhs) {
|
||||
using T = std::underlying_type_t<VerifyOutputFlags>;
|
||||
return static_cast<VerifyOutputFlags>(static_cast<T>(lhs) &
|
||||
static_cast<T>(rhs));
|
||||
}
|
||||
|
||||
inline VerifyOutputFlags& operator&=(VerifyOutputFlags& lhs,
|
||||
VerifyOutputFlags rhs) {
|
||||
lhs = lhs & rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline bool operator!(VerifyOutputFlags flag) {
|
||||
return flag == VerifyOutputFlags::kVerifyNone;
|
||||
}
|
||||
|
||||
struct AdvancedColumnFamilyOptions {
|
||||
// The maximum number of write buffers that are built up in memory.
|
||||
// The default and the minimum number is 2, so that when 1 write buffer
|
||||
@@ -473,6 +528,17 @@ struct AdvancedColumnFamilyOptions {
|
||||
// Dynamically changeable through SetOptions() API
|
||||
int target_file_size_multiplier = 1;
|
||||
|
||||
// If true, RocksDB will consider the estimated tail size (filter + index +
|
||||
// meta blocks) when deciding whether to cut a compaction output file. This
|
||||
// helps prevent output files from exceeding the target_file_size_base due to
|
||||
// large tail blocks. When disabled, only the data block size is considered,
|
||||
// which may result in SST files exceeding the target_file_size_base.
|
||||
//
|
||||
// Default: false
|
||||
//
|
||||
// Dynamically changeable through SetOptions() API
|
||||
bool target_file_size_is_upper_bound = false;
|
||||
|
||||
// If true, RocksDB will pick target size of each level dynamically.
|
||||
// We will pick a base level b >= 1. L0 will be directly merged into level b,
|
||||
// instead of always into level 1. Level 1 to b-1 need to be empty.
|
||||
@@ -704,6 +770,13 @@ struct AdvancedColumnFamilyOptions {
|
||||
// Dynamically changeable through SetOptions() API
|
||||
bool paranoid_file_checks = false;
|
||||
|
||||
// Bitmask enum for output verification option.
|
||||
//
|
||||
// Default: 0 (kVerifyNone)
|
||||
//
|
||||
// Dynamically changeable (as a uint32_t) through SetOptions() API.
|
||||
VerifyOutputFlags verify_output_flags = VerifyOutputFlags::kVerifyNone;
|
||||
|
||||
// In debug mode, RocksDB runs consistency checks on the LSM every time the
|
||||
// LSM changes (Flush, Compaction, AddFile). When this option is true, these
|
||||
// checks are also enabled in release mode. These checks were historically
|
||||
|
||||
@@ -148,6 +148,14 @@ typedef struct rocksdb_statistics_histogram_data_t
|
||||
rocksdb_statistics_histogram_data_t;
|
||||
typedef struct rocksdb_wait_for_compact_options_t
|
||||
rocksdb_wait_for_compact_options_t;
|
||||
|
||||
/* rocksdb_slice_t: Optimized slice type for high-performance C API operations
|
||||
* This struct is ABI-compatible with rocksdb::Slice for zero-copy interop.
|
||||
* Used by slice iterator functions and batched operations. */
|
||||
typedef struct rocksdb_slice_t {
|
||||
const char* data;
|
||||
size_t size;
|
||||
} rocksdb_slice_t;
|
||||
typedef struct rocksdb_flushjobinfo_t rocksdb_flushjobinfo_t;
|
||||
typedef struct rocksdb_compactionjobinfo_t rocksdb_compactionjobinfo_t;
|
||||
typedef struct rocksdb_subcompactionjobinfo_t rocksdb_subcompactionjobinfo_t;
|
||||
@@ -610,6 +618,16 @@ extern ROCKSDB_LIBRARY_API void rocksdb_batched_multi_get_cf(
|
||||
const char* const* keys_list, const size_t* keys_list_sizes,
|
||||
rocksdb_pinnableslice_t** values, char** errs, const bool sorted_input);
|
||||
|
||||
/* Batched MultiGet with slice array: Takes rocksdb_slice_t array directly,
|
||||
* avoiding key conversion. faster than rocksdb_batched_multi_get_cf for
|
||||
* operations with many keys. Eliminates overhead of converting keys from
|
||||
* separate pointer+size arrays to Slice objects. */
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_batched_multi_get_cf_slice(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, size_t num_keys,
|
||||
const rocksdb_slice_t* keys_list, rocksdb_pinnableslice_t** values,
|
||||
char** errs, const bool sorted_input);
|
||||
|
||||
// The value is only allocated (using malloc) and returned if it is found and
|
||||
// value_found isn't NULL. In that case the user is responsible for freeing it.
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_key_may_exist(
|
||||
@@ -776,6 +794,18 @@ extern ROCKSDB_LIBRARY_API const char* rocksdb_iter_timestamp(
|
||||
const rocksdb_iterator_t*, size_t* tslen);
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_iter_get_error(
|
||||
const rocksdb_iterator_t*, char** errptr);
|
||||
|
||||
/* Slice iterator functions: Return rocksdb_slice_t directly for better
|
||||
* performance. These functions avoid the overhead of passing output parameters
|
||||
* and provide zero-copy access to key/value/timestamp data. faster than
|
||||
* traditional rocksdb_iter_key/value/timestamp functions. */
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_slice_t
|
||||
rocksdb_iter_key_slice(const rocksdb_iterator_t* iter);
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_slice_t
|
||||
rocksdb_iter_value_slice(const rocksdb_iterator_t* iter);
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_slice_t
|
||||
rocksdb_iter_timestamp_slice(const rocksdb_iterator_t* iter);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_iter_refresh(
|
||||
const rocksdb_iterator_t* iter, char** errptr);
|
||||
|
||||
@@ -3440,6 +3470,48 @@ extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_wait_for_compact_options_get_timeout(
|
||||
rocksdb_wait_for_compact_options_t* opt);
|
||||
|
||||
/* High-performance zero-copy Get variants
|
||||
These functions avoid unnecessary memory allocations and copies.
|
||||
The returned buffer is valid until the handle is destroyed.
|
||||
Bindings should migrate to these for better performance. */
|
||||
|
||||
/* Zero-copy get that returns a handle to pinned data.
|
||||
The data remains valid until rocksdb_pinnable_handle_destroy is called.
|
||||
Returns NULL on error or not found. Check errptr to distinguish. */
|
||||
typedef struct rocksdb_pinnable_handle_t rocksdb_pinnable_handle_t;
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_pinnable_handle_t* rocksdb_get_pinned_v2(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
|
||||
size_t keylen, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API rocksdb_pinnable_handle_t* rocksdb_get_pinned_cf_v2(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
size_t keylen, char** errptr);
|
||||
|
||||
/* Get the data pointer and size from a pinnable handle.
|
||||
The data pointer is valid until the handle is destroyed. */
|
||||
extern ROCKSDB_LIBRARY_API const char* rocksdb_pinnable_handle_get_value(
|
||||
const rocksdb_pinnable_handle_t* handle, size_t* vallen);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API void rocksdb_pinnable_handle_destroy(
|
||||
rocksdb_pinnable_handle_t* handle);
|
||||
|
||||
/* Direct get into caller-provided buffer.
|
||||
Returns 1 if value fits in buffer, 0 if buffer too small.
|
||||
Sets *vallen to actual value size.
|
||||
If buffer is too small, no data is copied but *vallen is set. */
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_get_into_buffer(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options, const char* key,
|
||||
size_t keylen, char* buffer, size_t buffer_size, size_t* vallen,
|
||||
unsigned char* found, char** errptr);
|
||||
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_get_into_buffer_cf(
|
||||
rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
size_t keylen, char* buffer, size_t buffer_size, size_t* vallen,
|
||||
unsigned char* found, char** errptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* end extern "C" */
|
||||
#endif
|
||||
@@ -284,9 +284,7 @@ class CompactionFilter : public Customizable {
|
||||
std::string* new_value,
|
||||
std::vector<std::pair<std::string, std::string>>* /* new_columns */,
|
||||
std::string* skip_until) const {
|
||||
#ifdef NDEBUG
|
||||
(void)existing_columns;
|
||||
#endif
|
||||
|
||||
assert(!existing_value || !existing_columns);
|
||||
assert(value_type == ValueType::kWideColumnEntity || existing_value);
|
||||
|
||||
@@ -247,15 +247,7 @@ class ManagedPtr {
|
||||
public:
|
||||
ManagedPtr() = default;
|
||||
ManagedPtr(T* ptr, Owner* owner) : ptr_(ptr), owner_(owner) {}
|
||||
~ManagedPtr() {
|
||||
if (ptr_ && owner_) {
|
||||
if constexpr (std::is_member_function_pointer_v<decltype(Fn)>) {
|
||||
(owner_->*Fn)(ptr_);
|
||||
} else {
|
||||
Fn(owner_, ptr_);
|
||||
}
|
||||
}
|
||||
}
|
||||
~ManagedPtr() { Free(); }
|
||||
// No copies
|
||||
ManagedPtr(const ManagedPtr&) = delete;
|
||||
ManagedPtr& operator=(const ManagedPtr&) = delete;
|
||||
@@ -267,6 +259,10 @@ class ManagedPtr {
|
||||
other.owner_ = nullptr;
|
||||
}
|
||||
ManagedPtr& operator=(ManagedPtr&& other) noexcept {
|
||||
if (this == &other) {
|
||||
return *this;
|
||||
}
|
||||
Free();
|
||||
ptr_ = other.ptr_;
|
||||
owner_ = other.owner_;
|
||||
other.ptr_ = nullptr;
|
||||
@@ -284,6 +280,16 @@ class ManagedPtr {
|
||||
private:
|
||||
T* ptr_ = nullptr;
|
||||
Owner* owner_ = nullptr;
|
||||
|
||||
void Free() {
|
||||
if (ptr_ && owner_) {
|
||||
if constexpr (std::is_member_function_pointer_v<decltype(Fn)>) {
|
||||
(owner_->*Fn)(ptr_);
|
||||
} else {
|
||||
Fn(owner_, ptr_);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename comp>
|
||||
|
||||
+13
-6
@@ -35,12 +35,6 @@
|
||||
#include "rocksdb/version.h"
|
||||
#include "rocksdb/wide_columns.h"
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define ROCKSDB_DEPRECATED_FUNC __attribute__((__deprecated__))
|
||||
#elif _WIN32
|
||||
#define ROCKSDB_DEPRECATED_FUNC __declspec(deprecated)
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct ColumnFamilyOptions;
|
||||
@@ -1935,11 +1929,24 @@ class DB {
|
||||
virtual void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) {}
|
||||
|
||||
// Obtains the LSM-tree meta data of the specified column family of the DB
|
||||
// with optional filtering by key range and level.
|
||||
virtual void GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const GetColumnFamilyMetaDataOptions& /*options*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) {}
|
||||
|
||||
// Get the metadata of the default column family.
|
||||
void GetColumnFamilyMetaData(ColumnFamilyMetaData* metadata) {
|
||||
GetColumnFamilyMetaData(DefaultColumnFamily(), metadata);
|
||||
}
|
||||
|
||||
// Get the metadata of the default column family with optional filtering.
|
||||
void GetColumnFamilyMetaData(const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* metadata) {
|
||||
GetColumnFamilyMetaData(DefaultColumnFamily(), options, metadata);
|
||||
}
|
||||
|
||||
// Obtains the LSM-tree meta data of all column families of the DB, including
|
||||
// metadata for each live table (SST) file and each blob file in the DB.
|
||||
virtual void GetAllColumnFamilyMetaData(
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
#undef LoadLibrary
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#if defined(__GNUC__) || defined(__clang__) // ODR-SAFE (essentially)
|
||||
#define ROCKSDB_PRINTF_FORMAT_ATTR(format_param, dots_param) \
|
||||
__attribute__((__format__(__printf__, format_param, dots_param)))
|
||||
#else
|
||||
|
||||
@@ -558,7 +558,7 @@ class FileSystem : public Customizable {
|
||||
}
|
||||
|
||||
// This seems to clash with a macro on Windows, so #undef it here
|
||||
#ifdef DeleteFile
|
||||
#ifdef DeleteFile // ODR-SAFE
|
||||
#undef DeleteFile
|
||||
#endif
|
||||
// Delete the named file.
|
||||
@@ -719,7 +719,7 @@ class FileSystem : public Customizable {
|
||||
const ImmutableDBOptions& db_options) const;
|
||||
|
||||
// This seems to clash with a macro on Windows, so #undef it here
|
||||
#ifdef GetFreeSpace
|
||||
#ifdef GetFreeSpace // ODR-SAFE
|
||||
#undef GetFreeSpace
|
||||
#endif
|
||||
|
||||
@@ -1166,8 +1166,10 @@ class FSWritableFile {
|
||||
|
||||
// Truncate is necessary to trim the file to the correct size
|
||||
// before closing. It is not always possible to keep track of the file
|
||||
// size due to whole pages writes. The behavior is undefined if called
|
||||
// with other writes to follow.
|
||||
// size due to whole pages writes. If called with other writes to follow,
|
||||
// the behavior is file system specific. Posix will reseek to the new EOF.
|
||||
// Other file systems may behave differently. Its the caller's
|
||||
// responsibility to check the file system contract.
|
||||
virtual IOStatus Truncate(uint64_t /*size*/, const IOOptions& /*options*/,
|
||||
IODebugContext* /*dbg*/) {
|
||||
return IOStatus::OK();
|
||||
|
||||
@@ -224,6 +224,20 @@ struct LevelMetaData {
|
||||
const std::vector<SstFileMetaData> files;
|
||||
};
|
||||
|
||||
// Options for filtering column family metadata by key range.
|
||||
struct GetColumnFamilyMetaDataOptions {
|
||||
RangeOpt range;
|
||||
|
||||
// The level to filter on. If -1, all levels are included.
|
||||
int level = -1;
|
||||
|
||||
GetColumnFamilyMetaDataOptions() = default;
|
||||
|
||||
GetColumnFamilyMetaDataOptions(const OptSlice& _start_key,
|
||||
const OptSlice& _end_key, int _level = -1)
|
||||
: range(_start_key, _end_key), level(_level) {}
|
||||
};
|
||||
|
||||
// The metadata that describes a column family.
|
||||
struct ColumnFamilyMetaData {
|
||||
ColumnFamilyMetaData() : size(0), file_count(0), name("") {}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "rocksdb/version.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
|
||||
#ifdef max
|
||||
#ifdef max // ODR-SAFE
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
@@ -958,12 +958,67 @@ struct DBOptions {
|
||||
// Default: 0
|
||||
size_t recycle_log_file_num = 0;
|
||||
|
||||
// manifest file is rolled over on reaching this limit.
|
||||
// The older manifest file be deleted.
|
||||
// The default value is 1GB so that the manifest file can grow, but not
|
||||
// reach the limit of storage capacity.
|
||||
// The manifest file is rolled over on reaching this limit AND the
|
||||
// space amp limit described in max_manifest_space_amp_pct. More trade-off
|
||||
// details there.
|
||||
//
|
||||
// NOTE: this option used to be a hard limit, but that made this a dangerous
|
||||
// tuning parameter for optimizing manifest file size because the best
|
||||
// size really depends on the DB size and average SST file size (and other
|
||||
// settings). Now it is essentially a minimum for the auto-tuned max manifest
|
||||
// file size.
|
||||
//
|
||||
// Until the max_manifest_space_amp_pct feature is fully validated to show a
|
||||
// smaller default here like 1MB is appropriate, the default value is 1GB to
|
||||
// match historical behavior (without it being a hard limit in case of giant
|
||||
// compacted manifest size).
|
||||
//
|
||||
// This option is mutable with SetDBOptions(), taking effect on the next
|
||||
// manifest write (e.g. completed DB compaction or flush).
|
||||
uint64_t max_manifest_file_size = 1024 * 1024 * 1024;
|
||||
|
||||
// This option mostly replaces max_manifest_file_size to control an auto-tuned
|
||||
// balance of manifest write amplification and space amplification. A new
|
||||
// manifest file is created with the "compacted" contents of the old one when
|
||||
// current_manifest_size
|
||||
// >
|
||||
// max(max_manifest_file_size,
|
||||
// est_compacted_manifest_size * (1 + max_manifest_space_amp_pct/100))
|
||||
//
|
||||
// where est_compacted_manifest_size is an estimate of how big a new compacted
|
||||
// version of the current manifest would be. Currently, the estimate used is
|
||||
// the last newly-written manifest, in its "compacted" form.
|
||||
//
|
||||
// Space amplification in the manifest file might be less of a concern for
|
||||
// primary storage space and more of a concern for DB recover time and size of
|
||||
// backup files that aren't incremental between backups. To minimize manifest
|
||||
// churn on initial DB population, setting max_manifest_file_size to something
|
||||
// not too small, like 1MB, should suffice. Similarly, write amp on the
|
||||
// manifest file is likely not a direct concern but completed compactions and
|
||||
// flushes cannot (currently) be committed while the (relatively small)
|
||||
// manifest file is being compacted. Manifest compactions should not
|
||||
// interfere with user write latency or throughput unless the DB is
|
||||
// chronically stalling or close to stalling writes already.
|
||||
//
|
||||
// For this option to have a meaningful effect, it is recommended to set
|
||||
// max_manifest_file_size to something modest like 1MB. Then we can interpret
|
||||
// values for this option as follows, starting with minimum space amp and
|
||||
// maximum write amp:
|
||||
// * 0 - Every manifest write (flush, compaction, etc.) generates a whole new
|
||||
// manifest. Only useful for testing.
|
||||
// * very small - Doesn't take many manifest writes to generate a whole new
|
||||
// manifest.
|
||||
// * 100 - In a DB with pretty consistent number of SST files, etc., achieves
|
||||
// about 1.0 write amp (writing about 2x the theoretical minimum) and a max of
|
||||
// about 1.0 space amp (manifest up to 2x the compacted size).
|
||||
// * 500 - Recommended and default: 0.2 write amp and up to roughly 5.0 space
|
||||
// amp.
|
||||
// * 10000 - 0.01 write amp and up to 100 space amp on the manifest.
|
||||
//
|
||||
// This option is mutable with SetDBOptions(), taking effect on the next
|
||||
// manifest write (e.g. completed DB compaction or flush).
|
||||
int max_manifest_space_amp_pct = 500;
|
||||
|
||||
// Number of shards used for table cache.
|
||||
int table_cache_numshardbits = 6;
|
||||
|
||||
@@ -2383,11 +2438,17 @@ struct CompactionOptions {
|
||||
// "default_write_temperature"
|
||||
Temperature output_temperature_override = Temperature::kUnknown;
|
||||
|
||||
// Option to optimize the manual compaction by enabling trivial move for non
|
||||
// overlapping files.
|
||||
// Default: false
|
||||
bool allow_trivial_move;
|
||||
|
||||
CompactionOptions()
|
||||
: compression(kDisableCompressionOption),
|
||||
output_file_size_limit(std::numeric_limits<uint64_t>::max()),
|
||||
max_subcompactions(0),
|
||||
canceled(nullptr) {}
|
||||
canceled(nullptr),
|
||||
allow_trivial_move(false) {}
|
||||
};
|
||||
|
||||
// For level based compaction, we can configure if we want to skip/force
|
||||
|
||||
@@ -15,12 +15,6 @@
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/wide_columns.h"
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define ROCKSDB_DEPRECATED_FUNC __attribute__((__deprecated__))
|
||||
#elif _WIN32
|
||||
#define ROCKSDB_DEPRECATED_FUNC __declspec(deprecated)
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Comparator;
|
||||
@@ -117,7 +111,7 @@ class SstFileWriter {
|
||||
// REQUIRES: user_key is after any previously added point (Put/Merge/Delete)
|
||||
// key according to the comparator.
|
||||
// REQUIRES: comparator is *not* timestamp-aware.
|
||||
ROCKSDB_DEPRECATED_FUNC Status Add(const Slice& user_key, const Slice& value);
|
||||
[[deprecated]] Status Add(const Slice& user_key, const Slice& value);
|
||||
|
||||
// Add a Put key with value to currently opened file
|
||||
// REQUIRES: user_key is after any previously added point (Put/Merge/Delete)
|
||||
|
||||
@@ -692,6 +692,9 @@ enum Histograms : uint32_t {
|
||||
// Number of operations per transaction.
|
||||
NUM_OP_PER_TRANSACTION,
|
||||
|
||||
// MultiScan Prefill iterator Prepare cost
|
||||
MULTISCAN_PREPARE_ITERATORS,
|
||||
|
||||
HISTOGRAM_ENUM_MAX
|
||||
};
|
||||
|
||||
|
||||
@@ -22,24 +22,16 @@
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
#if !defined(NROCKSDB_THREAD_STATUS)
|
||||
#define ROCKSDB_USING_THREAD_STATUS
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// TODO(yhchiang): remove this function once c++14 is available
|
||||
// as std::max will be able to cover this.
|
||||
// Current MS compiler does not support constexpr
|
||||
template <int A, int B>
|
||||
struct constexpr_max {
|
||||
static const int result = (A > B) ? A : B;
|
||||
};
|
||||
|
||||
// A structure that describes the current status of a thread.
|
||||
// The status of active threads can be fetched using
|
||||
// ROCKSDB_NAMESPACE::GetThreadList().
|
||||
struct ThreadStatus {
|
||||
// Whether RocksDB was built with !NROCKSDB_THREAD_STATUS for
|
||||
// ROCKSDB_NAMESPACE::GetThreadList() to be supported.
|
||||
static const bool kEnabled;
|
||||
|
||||
// The type of a thread.
|
||||
enum ThreadType : int {
|
||||
HIGH_PRIORITY = 0, // RocksDB BG thread in high-pri thread pool
|
||||
@@ -102,8 +94,8 @@ struct ThreadStatus {
|
||||
|
||||
// The maximum number of properties of an operation.
|
||||
// This number should be set to the biggest NUM_XXX_PROPERTIES.
|
||||
static const int kNumOperationProperties =
|
||||
constexpr_max<NUM_COMPACTION_PROPERTIES, NUM_FLUSH_PROPERTIES>::result;
|
||||
static constexpr int kNumOperationProperties =
|
||||
std::max(int{NUM_COMPACTION_PROPERTIES}, int{NUM_FLUSH_PROPERTIES});
|
||||
|
||||
// The type used to refer to a thread state.
|
||||
// A state describes lower-level action of a thread
|
||||
|
||||
@@ -68,7 +68,7 @@ class EnvMirror : public EnvWrapper {
|
||||
assert(as == bs);
|
||||
return as;
|
||||
}
|
||||
#if defined(_MSC_VER)
|
||||
#if defined(_MSC_VER) // ODR-SAFE
|
||||
#pragma warning(push)
|
||||
// logical operation on address of string constant
|
||||
#pragma warning(disable : 4130)
|
||||
@@ -87,7 +87,7 @@ class EnvMirror : public EnvWrapper {
|
||||
*r = ar;
|
||||
return as;
|
||||
}
|
||||
#if defined(_MSC_VER)
|
||||
#if defined(_MSC_VER) // ODR-SAFE
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
Status DeleteFile(const std::string& f) override {
|
||||
|
||||
@@ -9,10 +9,6 @@
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
#ifdef FAILED
|
||||
#undef FAILED
|
||||
#endif
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class LDBCommandExecuteResult {
|
||||
|
||||
@@ -6,19 +6,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
// Try to migrate DB created with old_opts to be use new_opts.
|
||||
// Multiple column families is not supported.
|
||||
// It is best-effort. No guarantee to succeed.
|
||||
// A full compaction may be executed.
|
||||
// Prepares a database to be compatible with new_opts after using old_opts.
|
||||
// Restructures the LSM tree but does NOT apply new_opts - you must call
|
||||
// DB::Open(new_opts, dbname) afterward to actually use the new configuration.
|
||||
// It is best-effort with no guarantee to succeed. A full compaction may be
|
||||
// executed.
|
||||
//
|
||||
// Limitations: single column family only
|
||||
//
|
||||
// WARNING: using this to migrate from non-FIFO to FIFO compaction
|
||||
// with `Options::compaction_options_fifo.max_table_files_size` > 0 can cause
|
||||
// the whole DB to be dropped right after migration if the migrated data is
|
||||
// larger than `max_table_files_size`
|
||||
Status OptionChangeMigration(std::string dbname, const Options& old_opts,
|
||||
Status OptionChangeMigration(const std::string& dbname, const Options& old_opts,
|
||||
const Options& new_opts);
|
||||
|
||||
// Multi-CF version: Prepares a database with multiple column families to be
|
||||
// compatible with new options after using old options.
|
||||
//
|
||||
// REQUIREMENTS:
|
||||
// - old_cf_descs and new_cf_descs MUST have the same number of CFs
|
||||
// - old_cf_descs and new_cf_descs MUST have the same CF names IN THE SAME ORDER
|
||||
// - Adding or dropping CFs is NOT supported - use CreateColumnFamily() or
|
||||
// DropColumnFamily() separately before/after migration
|
||||
//
|
||||
// The function will return InvalidArgument status if these requirements are
|
||||
// violated.
|
||||
//
|
||||
// WARNING: using this to migrate from non-FIFO to FIFO compaction
|
||||
// with `max_table_files_size` > 0 can cause the whole DB to be dropped right
|
||||
// after migration if the migrated data is larger than `max_table_files_size`
|
||||
Status OptionChangeMigration(
|
||||
const std::string& dbname, const DBOptions& old_db_opts,
|
||||
const std::vector<ColumnFamilyDescriptor>& old_cf_descs,
|
||||
const DBOptions& new_db_opts,
|
||||
const std::vector<ColumnFamilyDescriptor>& new_cf_descs);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -456,6 +456,12 @@ class StackableDB : public DB {
|
||||
db_->GetColumnFamilyMetaData(column_family, cf_meta);
|
||||
}
|
||||
|
||||
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* metadata) override {
|
||||
db_->GetColumnFamilyMetaData(column_family, options, metadata);
|
||||
}
|
||||
|
||||
using DB::StartBlockCacheTrace;
|
||||
Status StartBlockCacheTrace(
|
||||
const TraceOptions& trace_options,
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
// NOTE: in 'main' development branch, this should be the *next*
|
||||
// minor or major version number planned for release.
|
||||
#define ROCKSDB_MAJOR 10
|
||||
#define ROCKSDB_MINOR 8
|
||||
#define ROCKSDB_PATCH 0
|
||||
#define ROCKSDB_MINOR 9
|
||||
#define ROCKSDB_PATCH 1
|
||||
|
||||
// Make it easy to do conditional compilation based on version checks, i.e.
|
||||
// #if ROCKSDB_VERSION_GE(4, 5, 6)
|
||||
|
||||
+57
-30
@@ -1210,6 +1210,9 @@ jint Java_org_rocksdb_RocksDB_getDirect(JNIEnv* env, jclass /*jdb*/,
|
||||
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice());
|
||||
}
|
||||
|
||||
if (s.IsNotFound()) {
|
||||
return ROCKSDB_NAMESPACE::KVException::kNotFound;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.Fetch();
|
||||
} catch (ROCKSDB_NAMESPACE::KVException& e) {
|
||||
@@ -1453,10 +1456,13 @@ jbyteArray Java_org_rocksdb_RocksDB_get__J_3BII(JNIEnv* env, jclass,
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
ROCKSDB_NAMESPACE::Status s =
|
||||
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), db->DefaultColumnFamily(),
|
||||
key.slice(), &value.pinnable_slice()));
|
||||
key.slice(), &value.pinnable_slice());
|
||||
if (s.IsNotFound()) {
|
||||
return nullptr;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.NewByteArray();
|
||||
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
@@ -1484,9 +1490,13 @@ jbyteArray Java_org_rocksdb_RocksDB_get__J_3BIIJ(JNIEnv* env, jclass,
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(),
|
||||
&value.pinnable_slice()));
|
||||
ROCKSDB_NAMESPACE::Status s =
|
||||
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(),
|
||||
&value.pinnable_slice());
|
||||
if (s.IsNotFound()) {
|
||||
return nullptr;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.NewByteArray();
|
||||
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
@@ -1509,11 +1519,13 @@ jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BII(JNIEnv* env, jclass,
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
db->Get(
|
||||
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
|
||||
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice()));
|
||||
ROCKSDB_NAMESPACE::Status s = db->Get(
|
||||
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
|
||||
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice());
|
||||
if (s.IsNotFound()) {
|
||||
return nullptr;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.NewByteArray();
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return nullptr;
|
||||
@@ -1538,10 +1550,13 @@ jbyteArray Java_org_rocksdb_RocksDB_get__JJ_3BIIJ(
|
||||
try {
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Get(*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(
|
||||
jropt_handle),
|
||||
cf_handle, key.slice(), &value.pinnable_slice()));
|
||||
ROCKSDB_NAMESPACE::Status s = db->Get(
|
||||
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
|
||||
cf_handle, key.slice(), &value.pinnable_slice());
|
||||
if (s.IsNotFound()) {
|
||||
return nullptr;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.NewByteArray();
|
||||
} catch (ROCKSDB_NAMESPACE::KVException&) {
|
||||
return nullptr;
|
||||
@@ -1563,10 +1578,13 @@ jint Java_org_rocksdb_RocksDB_get__J_3BII_3BII(JNIEnv* env, jclass,
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
ROCKSDB_NAMESPACE::Status s =
|
||||
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), db->DefaultColumnFamily(),
|
||||
key.slice(), &value.pinnable_slice()));
|
||||
key.slice(), &value.pinnable_slice());
|
||||
if (s.IsNotFound()) {
|
||||
return ROCKSDB_NAMESPACE::KVException::kNotFound;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.Fetch();
|
||||
|
||||
} catch (ROCKSDB_NAMESPACE::KVException& e) {
|
||||
@@ -1595,9 +1613,13 @@ jint Java_org_rocksdb_RocksDB_get__J_3BII_3BIIJ(JNIEnv* env, jclass,
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(),
|
||||
&value.pinnable_slice()));
|
||||
ROCKSDB_NAMESPACE::Status s =
|
||||
db->Get(ROCKSDB_NAMESPACE::ReadOptions(), cf_handle, key.slice(),
|
||||
&value.pinnable_slice());
|
||||
if (s.IsNotFound()) {
|
||||
return ROCKSDB_NAMESPACE::KVException::kNotFound;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.Fetch();
|
||||
|
||||
} catch (ROCKSDB_NAMESPACE::KVException& e) {
|
||||
@@ -1621,11 +1643,13 @@ jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BII(JNIEnv* env, jclass,
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env,
|
||||
db->Get(
|
||||
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
|
||||
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice()));
|
||||
ROCKSDB_NAMESPACE::Status s = db->Get(
|
||||
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
|
||||
db->DefaultColumnFamily(), key.slice(), &value.pinnable_slice());
|
||||
if (s.IsNotFound()) {
|
||||
return ROCKSDB_NAMESPACE::KVException::kNotFound;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.Fetch();
|
||||
|
||||
} catch (ROCKSDB_NAMESPACE::KVException& e) {
|
||||
@@ -1652,10 +1676,13 @@ jint Java_org_rocksdb_RocksDB_get__JJ_3BII_3BIIJ(
|
||||
ROCKSDB_NAMESPACE::JByteArraySlice key(env, jkey, jkey_off, jkey_len);
|
||||
ROCKSDB_NAMESPACE::JByteArrayPinnableSlice value(env, jval, jval_off,
|
||||
jval_len);
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(
|
||||
env, db->Get(*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(
|
||||
jropt_handle),
|
||||
cf_handle, key.slice(), &value.pinnable_slice()));
|
||||
ROCKSDB_NAMESPACE::Status s = db->Get(
|
||||
*reinterpret_cast<ROCKSDB_NAMESPACE::ReadOptions*>(jropt_handle),
|
||||
cf_handle, key.slice(), &value.pinnable_slice());
|
||||
if (s.IsNotFound()) {
|
||||
return ROCKSDB_NAMESPACE::KVException::kNotFound;
|
||||
}
|
||||
ROCKSDB_NAMESPACE::KVException::ThrowOnError(env, s);
|
||||
return value.Fetch();
|
||||
|
||||
} catch (ROCKSDB_NAMESPACE::KVException& e) {
|
||||
|
||||
@@ -352,6 +352,8 @@ const std::vector<std::pair<Histograms, std::string>> HistogramsNameMap = {
|
||||
{TABLE_OPEN_PREFETCH_TAIL_READ_BYTES,
|
||||
"rocksdb.table.open.prefetch.tail.read.bytes"},
|
||||
{NUM_OP_PER_TRANSACTION, "rocksdb.num.op.per.transaction"},
|
||||
{MULTISCAN_PREPARE_ITERATORS,
|
||||
"rocksdb.multiscan.op.prepare.iterators.micros"},
|
||||
};
|
||||
|
||||
std::shared_ptr<Statistics> CreateDBStatistics() {
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
const bool ThreadStatus::kEnabled = true;
|
||||
|
||||
std::string ThreadStatus::GetThreadTypeName(
|
||||
ThreadStatus::ThreadType thread_type) {
|
||||
switch (thread_type) {
|
||||
@@ -117,6 +119,7 @@ std::map<std::string, uint64_t> ThreadStatus::InterpretOperationProperties(
|
||||
}
|
||||
|
||||
#else
|
||||
const bool ThreadStatus::kEnabled = false;
|
||||
|
||||
std::string ThreadStatus::GetThreadTypeName(
|
||||
ThreadStatus::ThreadType /*thread_type*/) {
|
||||
@@ -159,5 +162,5 @@ std::map<std::string, uint64_t> ThreadStatus::InterpretOperationProperties(
|
||||
return std::map<std::string, uint64_t>();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
|
||||
thread_local ThreadStatusData* ThreadStatusUpdater::thread_status_data_ =
|
||||
nullptr;
|
||||
@@ -324,5 +324,5 @@ void ThreadStatusUpdater::SetThreadOperationProperty(int /*i*/,
|
||||
void ThreadStatusUpdater::IncreaseThreadOperationProperty(int /*i*/,
|
||||
uint64_t /*delta*/) {}
|
||||
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -47,7 +47,7 @@ class ColumnFamilyHandle;
|
||||
|
||||
// The structure that keeps constant information about a column family.
|
||||
struct ConstantColumnFamilyInfo {
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
public:
|
||||
ConstantColumnFamilyInfo(const void* _db_key, const std::string& _db_name,
|
||||
const std::string& _cf_name)
|
||||
@@ -55,13 +55,13 @@ struct ConstantColumnFamilyInfo {
|
||||
const void* db_key;
|
||||
const std::string db_name;
|
||||
const std::string cf_name;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
};
|
||||
|
||||
// the internal data-structure that is used to reflect the current
|
||||
// status of a thread using a set of atomic pointers.
|
||||
struct ThreadStatusData {
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
explicit ThreadStatusData() {
|
||||
enable_tracking.store(false);
|
||||
thread_id.store(0);
|
||||
@@ -86,7 +86,7 @@ struct ThreadStatusData {
|
||||
std::atomic<ThreadStatus::OperationStage> operation_stage;
|
||||
std::atomic<uint64_t> op_properties[ThreadStatus::kNumOperationProperties];
|
||||
std::atomic<ThreadStatus::StateType> state_type;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
};
|
||||
|
||||
// The class that stores and updates the status of the current thread
|
||||
@@ -190,7 +190,7 @@ class ThreadStatusUpdater {
|
||||
const std::vector<ColumnFamilyHandle*>& handles, bool check_exist);
|
||||
|
||||
protected:
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
// The thread-local variable for storing thread status.
|
||||
static thread_local ThreadStatusData* thread_status_data_;
|
||||
|
||||
@@ -220,7 +220,7 @@ class ThreadStatusUpdater {
|
||||
|
||||
#else
|
||||
static ThreadStatusData* thread_status_data_;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifndef NDEBUG
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap(
|
||||
const std::vector<ColumnFamilyHandle*>& handles, bool check_exist) {
|
||||
std::unique_lock<std::mutex> lock(thread_list_mutex_);
|
||||
@@ -37,7 +37,7 @@ void ThreadStatusUpdater::TEST_VerifyColumnFamilyInfoMap(
|
||||
const std::vector<ColumnFamilyHandle*>& /*handles*/, bool /*check_exist*/) {
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
#endif // !NDEBUG
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
thread_local ThreadStatusUpdater*
|
||||
ThreadStatusUtil::thread_updater_local_cache_ = nullptr;
|
||||
thread_local bool ThreadStatusUtil::thread_updater_initialized_ = false;
|
||||
@@ -171,9 +171,10 @@ AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() {
|
||||
ThreadStatusUpdater* ThreadStatusUtil::thread_updater_local_cache_ = nullptr;
|
||||
bool ThreadStatusUtil::thread_updater_initialized_ = false;
|
||||
|
||||
bool ThreadStatusUtil::MaybeInitThreadLocalUpdater(const Env* /*env*/) {
|
||||
return false;
|
||||
}
|
||||
void ThreadStatusUtil::RegisterThread(
|
||||
const Env* /*env*/, ThreadStatus::ThreadType /*thread_type*/) {}
|
||||
|
||||
void ThreadStatusUtil::UnregisterThread() {}
|
||||
|
||||
void ThreadStatusUtil::SetEnableTracking(bool /*enable_tracking*/) {}
|
||||
|
||||
@@ -204,11 +205,15 @@ void ThreadStatusUtil::EraseDatabaseInfo(const DB* /*db*/) {}
|
||||
|
||||
void ThreadStatusUtil::ResetThreadStatus() {}
|
||||
|
||||
bool ThreadStatusUtil::MaybeInitThreadLocalUpdater(const Env* /*env*/) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoThreadOperationStageUpdater::AutoThreadOperationStageUpdater(
|
||||
ThreadStatus::OperationStage /*stage*/) {}
|
||||
|
||||
AutoThreadOperationStageUpdater::~AutoThreadOperationStageUpdater() {}
|
||||
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -90,7 +90,7 @@ class ThreadStatusUtil {
|
||||
// a non-null pointer.
|
||||
static bool MaybeInitThreadLocalUpdater(const Env* env);
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
// A boolean flag indicating whether thread_updater_local_cache_
|
||||
// is initialized. It is set to true when an Env uses any
|
||||
// ThreadStatusUtil functions using the current thread other
|
||||
@@ -130,7 +130,7 @@ class AutoThreadOperationStageUpdater {
|
||||
explicit AutoThreadOperationStageUpdater(ThreadStatus::OperationStage stage);
|
||||
~AutoThreadOperationStageUpdater();
|
||||
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
private:
|
||||
ThreadStatus::OperationStage prev_stage_;
|
||||
#endif
|
||||
|
||||
+15
-3
@@ -395,6 +395,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct MutableCFOptions, paranoid_file_checks),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"verify_output_flags",
|
||||
{offsetof(struct MutableCFOptions, verify_output_flags),
|
||||
OptionType::kUInt32T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"verify_checksums_in_compaction",
|
||||
{0, OptionType::kBoolean, OptionVerificationType::kDeprecated,
|
||||
OptionTypeFlags::kMutable}},
|
||||
@@ -450,6 +454,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct MutableCFOptions, target_file_size_multiplier),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"target_file_size_is_upper_bound",
|
||||
{offsetof(struct MutableCFOptions, target_file_size_is_upper_bound),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"arena_block_size",
|
||||
{offsetof(struct MutableCFOptions, arena_block_size),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
@@ -1070,10 +1078,12 @@ uint64_t MultiplyCheckOverflow(uint64_t op1, double op2) {
|
||||
if (op1 == 0 || op2 <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (std::numeric_limits<uint64_t>::max() / op1 < op2) {
|
||||
return op1;
|
||||
|
||||
if (op1 * op2 < static_cast<double>(std::numeric_limits<uint64_t>::max())) {
|
||||
return static_cast<uint64_t>(op1 * op2);
|
||||
}
|
||||
return static_cast<uint64_t>(op1 * op2);
|
||||
|
||||
return op1;
|
||||
}
|
||||
|
||||
// when level_compaction_dynamic_level_bytes is true and leveled compaction
|
||||
@@ -1168,6 +1178,8 @@ void MutableCFOptions::Dump(Logger* log) const {
|
||||
target_file_size_base);
|
||||
ROCKS_LOG_INFO(log, " target_file_size_multiplier: %d",
|
||||
target_file_size_multiplier);
|
||||
ROCKS_LOG_INFO(log, " target_file_size_is_upper_bound: %d",
|
||||
target_file_size_is_upper_bound);
|
||||
ROCKS_LOG_INFO(log, " max_bytes_for_level_base: %" PRIu64,
|
||||
max_bytes_for_level_base);
|
||||
ROCKS_LOG_INFO(log, " max_bytes_for_level_multiplier: %f",
|
||||
|
||||
@@ -132,6 +132,8 @@ struct MutableCFOptions {
|
||||
max_compaction_bytes(options.max_compaction_bytes),
|
||||
target_file_size_base(options.target_file_size_base),
|
||||
target_file_size_multiplier(options.target_file_size_multiplier),
|
||||
target_file_size_is_upper_bound(
|
||||
options.target_file_size_is_upper_bound),
|
||||
max_bytes_for_level_base(options.max_bytes_for_level_base),
|
||||
max_bytes_for_level_multiplier(options.max_bytes_for_level_multiplier),
|
||||
ttl(options.ttl),
|
||||
@@ -143,6 +145,7 @@ struct MutableCFOptions {
|
||||
preclude_last_level_data_seconds(
|
||||
options.preclude_last_level_data_seconds),
|
||||
preserve_internal_time_seconds(options.preserve_internal_time_seconds),
|
||||
verify_output_flags(options.verify_output_flags),
|
||||
enable_blob_files(options.enable_blob_files),
|
||||
min_blob_size(options.min_blob_size),
|
||||
blob_file_size(options.blob_file_size),
|
||||
@@ -206,6 +209,7 @@ struct MutableCFOptions {
|
||||
max_compaction_bytes(0),
|
||||
target_file_size_base(0),
|
||||
target_file_size_multiplier(0),
|
||||
target_file_size_is_upper_bound(false),
|
||||
max_bytes_for_level_base(0),
|
||||
max_bytes_for_level_multiplier(0),
|
||||
ttl(0),
|
||||
@@ -213,6 +217,7 @@ struct MutableCFOptions {
|
||||
compaction_options_fifo(),
|
||||
preclude_last_level_data_seconds(0),
|
||||
preserve_internal_time_seconds(0),
|
||||
verify_output_flags(VerifyOutputFlags::kVerifyNone),
|
||||
enable_blob_files(false),
|
||||
min_blob_size(0),
|
||||
blob_file_size(0),
|
||||
@@ -304,6 +309,7 @@ struct MutableCFOptions {
|
||||
uint64_t max_compaction_bytes;
|
||||
uint64_t target_file_size_base;
|
||||
int target_file_size_multiplier;
|
||||
bool target_file_size_is_upper_bound;
|
||||
uint64_t max_bytes_for_level_base;
|
||||
double max_bytes_for_level_multiplier;
|
||||
uint64_t ttl;
|
||||
@@ -313,6 +319,7 @@ struct MutableCFOptions {
|
||||
CompactionOptionsUniversal compaction_options_universal;
|
||||
uint64_t preclude_last_level_data_seconds;
|
||||
uint64_t preserve_internal_time_seconds;
|
||||
VerifyOutputFlags verify_output_flags;
|
||||
|
||||
// Blob file related options
|
||||
bool enable_blob_files;
|
||||
|
||||
+27
-36
@@ -124,6 +124,18 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct MutableDBOptions, max_background_flushes),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"max_manifest_file_size",
|
||||
{offsetof(struct MutableDBOptions, max_manifest_file_size),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"max_manifest_space_amp_pct",
|
||||
{offsetof(struct MutableDBOptions, max_manifest_space_amp_pct),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"manifest_preallocation_size",
|
||||
{offsetof(struct MutableDBOptions, manifest_preallocation_size),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"daily_offpeak_time_utc",
|
||||
{offsetof(struct MutableDBOptions, daily_offpeak_time_utc),
|
||||
OptionType::kString, OptionVerificationType::kNormal,
|
||||
@@ -288,10 +300,6 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct ImmutableDBOptions, log_file_time_to_roll),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"manifest_preallocation_size",
|
||||
{offsetof(struct ImmutableDBOptions, manifest_preallocation_size),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"max_log_file_size",
|
||||
{offsetof(struct ImmutableDBOptions, max_log_file_size),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
@@ -310,10 +318,6 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct ImmutableDBOptions, WAL_ttl_seconds),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"max_manifest_file_size",
|
||||
{offsetof(struct ImmutableDBOptions, max_manifest_file_size),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"persist_stats_to_disk",
|
||||
{offsetof(struct ImmutableDBOptions, persist_stats_to_disk),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
@@ -657,7 +661,7 @@ class DBOptionsConfigurable : public MutableDBConfigurable {
|
||||
explicit DBOptionsConfigurable(
|
||||
const DBOptions& opts,
|
||||
const std::unordered_map<std::string, std::string>* map = nullptr)
|
||||
: MutableDBConfigurable(MutableDBOptions(opts), map), db_options_(opts) {
|
||||
: MutableDBConfigurable(MutableDBOptions{opts}, map), db_options_(opts) {
|
||||
// The ImmutableDBOptions currently requires the env to be non-null. Make
|
||||
// sure it is
|
||||
if (opts.env != nullptr) {
|
||||
@@ -708,7 +712,7 @@ std::unique_ptr<Configurable> DBOptionsAsConfigurable(
|
||||
return ptr;
|
||||
}
|
||||
|
||||
ImmutableDBOptions::ImmutableDBOptions() : ImmutableDBOptions(Options()) {}
|
||||
ImmutableDBOptions::ImmutableDBOptions() : ImmutableDBOptions(DBOptions{}) {}
|
||||
|
||||
ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
: create_if_missing(options.create_if_missing),
|
||||
@@ -737,13 +741,11 @@ ImmutableDBOptions::ImmutableDBOptions(const DBOptions& options)
|
||||
log_file_time_to_roll(options.log_file_time_to_roll),
|
||||
keep_log_file_num(options.keep_log_file_num),
|
||||
recycle_log_file_num(options.recycle_log_file_num),
|
||||
max_manifest_file_size(options.max_manifest_file_size),
|
||||
table_cache_numshardbits(options.table_cache_numshardbits),
|
||||
WAL_ttl_seconds(options.WAL_ttl_seconds),
|
||||
WAL_size_limit_MB(options.WAL_size_limit_MB),
|
||||
max_write_batch_group_size_bytes(
|
||||
options.max_write_batch_group_size_bytes),
|
||||
manifest_preallocation_size(options.manifest_preallocation_size),
|
||||
allow_mmap_reads(options.allow_mmap_reads),
|
||||
allow_mmap_writes(options.allow_mmap_writes),
|
||||
use_direct_reads(options.use_direct_reads),
|
||||
@@ -850,9 +852,6 @@ void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " Options.max_log_file_size: %" ROCKSDB_PRIszt,
|
||||
max_log_file_size);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.max_manifest_file_size: %" PRIu64,
|
||||
max_manifest_file_size);
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " Options.log_file_time_to_roll: %" ROCKSDB_PRIszt,
|
||||
log_file_time_to_roll);
|
||||
@@ -892,9 +891,6 @@ void ImmutableDBOptions::Dump(Logger* log) const {
|
||||
" "
|
||||
"Options.max_write_batch_group_size_bytes: %" PRIu64,
|
||||
max_write_batch_group_size_bytes);
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " Options.manifest_preallocation_size: %" ROCKSDB_PRIszt,
|
||||
manifest_preallocation_size);
|
||||
ROCKS_LOG_HEADER(log, " Options.is_fd_close_on_exec: %d",
|
||||
is_fd_close_on_exec);
|
||||
ROCKS_LOG_HEADER(log, " Options.advise_random_on_open: %d",
|
||||
@@ -1025,24 +1021,7 @@ const std::string& ImmutableDBOptions::GetWalDir(
|
||||
}
|
||||
}
|
||||
|
||||
MutableDBOptions::MutableDBOptions()
|
||||
: max_background_jobs(2),
|
||||
max_background_compactions(-1),
|
||||
max_subcompactions(0),
|
||||
avoid_flush_during_shutdown(false),
|
||||
writable_file_max_buffer_size(1024 * 1024),
|
||||
delayed_write_rate(2 * 1024U * 1024U),
|
||||
max_total_wal_size(0),
|
||||
delete_obsolete_files_period_micros(6ULL * 60 * 60 * 1000000),
|
||||
stats_dump_period_sec(600),
|
||||
stats_persist_period_sec(600),
|
||||
stats_history_buffer_size(1024 * 1024),
|
||||
max_open_files(-1),
|
||||
bytes_per_sync(0),
|
||||
wal_bytes_per_sync(0),
|
||||
strict_bytes_per_sync(false),
|
||||
compaction_readahead_size(0),
|
||||
max_background_flushes(-1) {}
|
||||
MutableDBOptions::MutableDBOptions() : MutableDBOptions(DBOptions{}) {}
|
||||
|
||||
MutableDBOptions::MutableDBOptions(const DBOptions& options)
|
||||
: max_background_jobs(options.max_background_jobs),
|
||||
@@ -1063,6 +1042,9 @@ MutableDBOptions::MutableDBOptions(const DBOptions& options)
|
||||
strict_bytes_per_sync(options.strict_bytes_per_sync),
|
||||
compaction_readahead_size(options.compaction_readahead_size),
|
||||
max_background_flushes(options.max_background_flushes),
|
||||
max_manifest_file_size(options.max_manifest_file_size),
|
||||
max_manifest_space_amp_pct(options.max_manifest_space_amp_pct),
|
||||
manifest_preallocation_size(options.manifest_preallocation_size),
|
||||
daily_offpeak_time_utc(options.daily_offpeak_time_utc) {}
|
||||
|
||||
void MutableDBOptions::Dump(Logger* log) const {
|
||||
@@ -1107,6 +1089,15 @@ void MutableDBOptions::Dump(Logger* log) const {
|
||||
compaction_readahead_size);
|
||||
ROCKS_LOG_HEADER(log, " Options.max_background_flushes: %d",
|
||||
max_background_flushes);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.max_manifest_file_size: %" PRIu64,
|
||||
max_manifest_file_size);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.max_manifest_space_amp_pct: %d",
|
||||
max_manifest_space_amp_pct);
|
||||
ROCKS_LOG_HEADER(
|
||||
log, " Options.manifest_preallocation_size: %" ROCKSDB_PRIszt,
|
||||
manifest_preallocation_size);
|
||||
ROCKS_LOG_HEADER(log, "Options.daily_offpeak_time_utc: %s",
|
||||
daily_offpeak_time_utc.c_str());
|
||||
}
|
||||
|
||||
@@ -47,12 +47,10 @@ struct ImmutableDBOptions {
|
||||
size_t log_file_time_to_roll;
|
||||
size_t keep_log_file_num;
|
||||
size_t recycle_log_file_num;
|
||||
uint64_t max_manifest_file_size;
|
||||
int table_cache_numshardbits;
|
||||
uint64_t WAL_ttl_seconds;
|
||||
uint64_t WAL_size_limit_MB;
|
||||
uint64_t max_write_batch_group_size_bytes;
|
||||
size_t manifest_preallocation_size;
|
||||
bool allow_mmap_reads;
|
||||
bool allow_mmap_writes;
|
||||
bool use_direct_reads;
|
||||
@@ -146,6 +144,9 @@ struct MutableDBOptions {
|
||||
bool strict_bytes_per_sync;
|
||||
size_t compaction_readahead_size;
|
||||
int max_background_flushes;
|
||||
uint64_t max_manifest_file_size;
|
||||
int max_manifest_space_amp_pct;
|
||||
size_t manifest_preallocation_size;
|
||||
std::string daily_offpeak_time_utc;
|
||||
};
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ AdvancedColumnFamilyOptions::AdvancedColumnFamilyOptions(const Options& options)
|
||||
level0_stop_writes_trigger(options.level0_stop_writes_trigger),
|
||||
target_file_size_base(options.target_file_size_base),
|
||||
target_file_size_multiplier(options.target_file_size_multiplier),
|
||||
target_file_size_is_upper_bound(options.target_file_size_is_upper_bound),
|
||||
level_compaction_dynamic_level_bytes(
|
||||
options.level_compaction_dynamic_level_bytes),
|
||||
max_bytes_for_level_multiplier(options.max_bytes_for_level_multiplier),
|
||||
@@ -269,6 +270,9 @@ void ColumnFamilyOptions::Dump(Logger* log) const {
|
||||
target_file_size_base);
|
||||
ROCKS_LOG_HEADER(log, " Options.target_file_size_multiplier: %d",
|
||||
target_file_size_multiplier);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.target_file_size_is_upper_bound: %d",
|
||||
target_file_size_is_upper_bound);
|
||||
ROCKS_LOG_HEADER(log,
|
||||
" Options.max_bytes_for_level_base: %" PRIu64,
|
||||
max_bytes_for_level_base);
|
||||
|
||||
Loaded 100 of 160 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user