mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 03:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa616f0e79 | ||
|
|
96db15c6e6 | ||
|
|
912596031a | ||
|
|
f4fe40845a | ||
|
|
40a5f27f6e | ||
|
|
9676ce2f84 | ||
|
|
e506aa7154 | ||
|
|
6d4c2c3297 | ||
|
|
2ceb3ef4d2 | ||
|
|
edc76202a2 | ||
|
|
b44c70adcf | ||
|
|
71eb5a1646 | ||
|
|
63a256d768 | ||
|
|
87d4b0d579 | ||
|
|
09bc20b16e | ||
|
|
dd9a806edd | ||
|
|
ad218cacf6 | ||
|
|
6a79e02ebd | ||
|
|
a9906f0dd0 | ||
|
|
b89d290c20 | ||
|
|
f84351de98 | ||
|
|
f312633aff | ||
|
|
a6af317476 | ||
|
|
eb5e1a2d1f | ||
|
|
ea5e649225 | ||
|
|
2a7a6a6d72 | ||
|
|
88aff40c97 | ||
|
|
c6d08d3efe | ||
|
|
a1af6f9f64 | ||
|
|
57036b68d9 | ||
|
|
256838180e | ||
|
|
2893c25ca2 | ||
|
|
a6325e9564 | ||
|
|
4bcec5ae89 | ||
|
|
4bf2bcdbb3 | ||
|
|
2b28885c80 | ||
|
|
429b36c22d | ||
|
|
6f03c3dfea | ||
|
|
a7c1acbe9f | ||
|
|
4e10e0bcac | ||
|
|
387cb4aae7 | ||
|
|
8b6f98cdb4 | ||
|
|
1cc1df8dab | ||
|
|
3818cc1aca | ||
|
|
276721cd10 | ||
|
|
45690d0f6a | ||
|
|
e77ba4bc95 | ||
|
|
7e9f54d56b | ||
|
|
41beb1422f | ||
|
|
9065ace05a | ||
|
|
5a06787a26 | ||
|
|
a1d8318563 | ||
|
|
eedf1fe068 | ||
|
|
80c4a67d6a | ||
|
|
5d0cf98e6c | ||
|
|
e3b5464785 | ||
|
|
7c48905ecd | ||
|
|
707e405492 | ||
|
|
340ac7ea6b | ||
|
|
d2fe0ee389 | ||
|
|
4951494a27 | ||
|
|
ac412b1095 | ||
|
|
9e14d06143 | ||
|
|
9c2c8f54fa | ||
|
|
42ba71fbbf | ||
|
|
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 }}
|
||||
@@ -0,0 +1,21 @@
|
||||
name: cache-getdeps-downloads
|
||||
description: Cache getdeps downloads to avoid unreliable mirrors and speed up builds
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: Whether the cache was hit
|
||||
value: ${{ steps.cache-downloads.outputs.cache-hit }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Cache getdeps downloads
|
||||
id: cache-downloads
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
# Use a fixed path that we control - folly.mk will sync with getdeps downloads dir
|
||||
path: /tmp/rocksdb-getdeps-cache
|
||||
# Use a rolling cache key - the cache accumulates downloads over time
|
||||
# The key includes a weekly timestamp to ensure periodic refresh
|
||||
key: getdeps-downloads-${{ runner.os }}-${{ runner.arch }}-week-${{ github.run_id }}
|
||||
restore-keys: |
|
||||
getdeps-downloads-${{ runner.os }}-${{ runner.arch }}-week-
|
||||
getdeps-downloads-${{ runner.os }}-${{ runner.arch }}-
|
||||
@@ -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
|
||||
@@ -54,11 +54,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/cache-getdeps-downloads"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- 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 +69,30 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: make V=1 -j32 valgrind_test
|
||||
- uses: "./.github/actions/cache-getdeps-downloads"
|
||||
- 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/cache-getdeps-downloads"
|
||||
- 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' }}
|
||||
@@ -89,6 +113,20 @@ jobs:
|
||||
- run: sudo apt-get update && sudo apt-get install -y build-essential libgflags-dev
|
||||
- run: make V=1 J=4 -j4 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-arm-crashtest:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: sudo apt-get update && sudo apt-get install -y build-essential libgflags-dev libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
|
||||
- run: sudo mount -o remount,size=16G /dev/shm
|
||||
- run: sudo dd bs=1048576 count=4096 if=/dev/zero of=/swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
|
||||
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=1800 --max_key=2500000' blackbox_crash_test_with_atomic_flush
|
||||
- run: rm -rf /dev/shm/rocksdb.*
|
||||
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=1800 --max_key=2500000' blackbox_crash_test_with_multiops_wc_txn
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-examples:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
@@ -117,7 +155,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
|
||||
@@ -127,6 +165,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/cache-getdeps-downloads"
|
||||
- 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:
|
||||
@@ -120,6 +102,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/cache-getdeps-downloads"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- uses: "./.github/actions/cache-folly"
|
||||
id: cache-folly
|
||||
@@ -138,6 +121,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/cache-getdeps-downloads"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: USE_FOLLY_LITE=1 EXTRA_CXXFLAGS=-DGLOG_USE_GLOG_EXPORT V=1 make -j32 all
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -151,15 +135,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/cache-getdeps-downloads"
|
||||
- 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_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 +154,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 +357,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",
|
||||
@@ -266,6 +268,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"util/dynamic_bloom.cc",
|
||||
"util/file_checksum_helper.cc",
|
||||
"util/hash.cc",
|
||||
"util/io_dispatcher_imp.cc",
|
||||
"util/murmurhash.cc",
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
@@ -4844,6 +4847,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 +5203,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"],
|
||||
@@ -5212,6 +5215,12 @@ cpp_unittest_wrapper(name="interval_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="io_dispatcher_test",
|
||||
srcs=["util/io_dispatcher_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="io_posix_test",
|
||||
srcs=["env/io_posix_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
+27
-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)
|
||||
@@ -213,6 +219,10 @@ if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4996 /wd4100 /wd4324")
|
||||
endif()
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DNDEBUG")
|
||||
message(STATUS "Setting /DNDEBUG as CMAKE_BUILD_TYPE is set to ${CMAKE_BUILD_TYPE}")
|
||||
endif()
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing -Wno-invalid-offsetof")
|
||||
@@ -642,6 +652,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 +1098,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 +1384,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 +1478,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
|
||||
|
||||
+71
@@ -1,6 +1,77 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 10.11.2 (02/25/2026)
|
||||
### Bug Fixes
|
||||
* Fix longstanding failures that can arise from reading and/or compacting old DB dirs with range deletions (likely from version < 5.19.0) in many newer versions.
|
||||
|
||||
## 10.11.1 (02/06/2026)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in PosixFS AbortIO that could cause it to ignore an IO completion and a subsequent Poll to hang
|
||||
|
||||
## 10.11.0 (01/23/2026)
|
||||
### Public API Changes
|
||||
* New SetOptions API that allows setting options for multiple CFs, avoiding the need to reserialize OPTIONS file for each CF
|
||||
* Remove remaining pieces of Lua integration
|
||||
|
||||
### Behavior Changes
|
||||
* The new default for `BlockBasedTableOptions::format_version` is 7, which has been supported since RocksDB 10.4.0 and is required in order to use CompressionManagers supporting custom compression types.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a small performance bug with `format_version=7` when decompressing formats other than Snappy and ZSTD.
|
||||
* Fixed an infinite compaction loop bug with User-Defined Timestamps (UDT) where bottommost files were repeatedly marked for compaction even though their timestamp could not be collapsed.
|
||||
* Bugfix for persisted UDT record sequence number zeroing logic.
|
||||
|
||||
## 10.10.0 (12/16/2025)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in best-efforts recovery that causes use-after-free crashes when accessing SST files that were cached during the recovery.
|
||||
* Fix resumable compaction incorrectly allowing resumption from a truncated range deletion that is not well handled currently.
|
||||
* Fixed a bug in `PosixRandomFileAccess` IO uring submission queue ownership & management. Fix eliminates the false positive 'Bad cqe data' IO errors in `PosixRandomFileAccess::MultiRead` when interleaved with `PosixRandomFileAccess::ReadAsync` on the same thread.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -296,6 +296,28 @@ $(info $(shell $(CC) --version))
|
||||
$(info $(shell $(CXX) --version))
|
||||
endif
|
||||
|
||||
# ccache support
|
||||
# Set USE_CCACHE=1 to enable ccache, or let it auto-detect
|
||||
ifndef USE_CCACHE
|
||||
CCACHE := $(shell which ccache 2>/dev/null)
|
||||
ifneq ($(CCACHE),)
|
||||
USE_CCACHE := 1
|
||||
else
|
||||
USE_CCACHE := 0
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(USE_CCACHE), 1)
|
||||
CCACHE := $(shell which ccache 2>/dev/null)
|
||||
ifneq ($(CCACHE),)
|
||||
$(info Using ccache: $(CCACHE))
|
||||
CC := $(CCACHE) $(CC)
|
||||
CXX := $(CCACHE) $(CXX)
|
||||
else
|
||||
$(warning ccache requested but not found in PATH)
|
||||
endif
|
||||
endif
|
||||
|
||||
missing_make_config_paths := $(shell \
|
||||
grep "\./\S*\|/\S*" -o $(CURDIR)/make_config.mk | \
|
||||
while read path; \
|
||||
@@ -368,8 +390,6 @@ ifdef COMPILE_WITH_TSAN
|
||||
# Turn off -pg when enabling TSAN testing, because that induces
|
||||
# a link failure. TODO: find the root cause
|
||||
PROFILING_FLAGS =
|
||||
# LUA is not supported under TSAN
|
||||
LUA_PATH =
|
||||
# Limit keys for crash test under TSAN to avoid error:
|
||||
# "ThreadSanitizer: DenseSlabAllocator overflow. Dying."
|
||||
CRASH_TEST_EXT_ARGS += --max_key=1000000
|
||||
@@ -446,83 +466,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)
|
||||
@@ -562,32 +506,6 @@ ifndef DISABLE_WARNING_AS_ERROR
|
||||
endif
|
||||
|
||||
|
||||
ifdef LUA_PATH
|
||||
|
||||
ifndef LUA_INCLUDE
|
||||
LUA_INCLUDE=$(LUA_PATH)/include
|
||||
endif
|
||||
|
||||
LUA_INCLUDE_FILE=$(LUA_INCLUDE)/lualib.h
|
||||
|
||||
ifeq ("$(wildcard $(LUA_INCLUDE_FILE))", "")
|
||||
# LUA_INCLUDE_FILE does not exist
|
||||
$(error Cannot find lualib.h under $(LUA_INCLUDE). Try to specify both LUA_PATH and LUA_INCLUDE manually)
|
||||
endif
|
||||
LUA_FLAGS = -I$(LUA_INCLUDE) -DLUA -DLUA_COMPAT_ALL
|
||||
CFLAGS += $(LUA_FLAGS)
|
||||
CXXFLAGS += $(LUA_FLAGS)
|
||||
|
||||
ifndef LUA_LIB
|
||||
LUA_LIB = $(LUA_PATH)/lib/liblua.a
|
||||
endif
|
||||
ifeq ("$(wildcard $(LUA_LIB))", "") # LUA_LIB does not exist
|
||||
$(error $(LUA_LIB) does not exist. Try to specify both LUA_PATH and LUA_LIB manually)
|
||||
endif
|
||||
EXEC_LDFLAGS += $(LUA_LIB)
|
||||
|
||||
endif
|
||||
|
||||
ifeq ($(NO_THREEWAY_CRC32C), 1)
|
||||
CXXFLAGS += -DNO_THREEWAY_CRC32C
|
||||
endif
|
||||
@@ -658,8 +576,8 @@ ifneq ($(filter check-headers, $(MAKECMDGOALS)),)
|
||||
# TODO: add/support JNI headers
|
||||
DEV_HEADER_DIRS := $(sort include/ $(dir $(ALL_SOURCES)))
|
||||
# Some headers like in port/ are platform-specific
|
||||
DEV_HEADERS_TO_CHECK := $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|lua/|range_tree/|secondary_index/')
|
||||
PUBLIC_HEADERS_TO_CHECK := $(shell $(FIND) include/ -type f -name '*.h' | grep -E -v 'lua/')
|
||||
DEV_HEADERS_TO_CHECK := $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | grep -E -v 'port/|plugin/|range_tree/|secondary_index/')
|
||||
PUBLIC_HEADERS_TO_CHECK := $(shell $(FIND) include/ -type f -name '*.h')
|
||||
else
|
||||
DEV_HEADERS_TO_CHECK :=
|
||||
PUBLIC_HEADERS_TO_CHECK :=
|
||||
@@ -683,6 +601,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 +1415,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 +1661,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)
|
||||
|
||||
@@ -2003,6 +1922,9 @@ blob_source_test: $(OBJ_DIR)/db/blob/blob_source_test.o $(TEST_LIBRARY) $(LIBRAR
|
||||
blob_garbage_meter_test: $(OBJ_DIR)/db/blob/blob_garbage_meter_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
io_dispatcher_test: $(OBJ_DIR)/util/io_dispatcher_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
timer_test: $(OBJ_DIR)/util/timer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -2495,42 +2417,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
|
||||
@@ -827,7 +831,6 @@ fi
|
||||
if test -n "$WITH_JEMALLOC_FLAG"; then
|
||||
echo "WITH_JEMALLOC_FLAG=$WITH_JEMALLOC_FLAG" >> "$OUTPUT"
|
||||
fi
|
||||
echo "LUA_PATH=$LUA_PATH" >> "$OUTPUT"
|
||||
if test -n "$USE_FOLLY"; then
|
||||
echo "USE_FOLLY=$USE_FOLLY" >> "$OUTPUT"
|
||||
fi
|
||||
|
||||
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 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|_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
|
||||
@@ -19,4 +19,3 @@ BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/780c7a0f9cf0967961e69ad08e61cddd
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/624a2f8f6c93c3c1df8aa4a6255d8202631a6c80/fb/platform010/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/39579e8603b48b3540f8b0633f43adf29acccb8b/2.37/centos8-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/cd9cc656d49ecb53797ce4d055e49fde29fd57ff/3.19.0/platform010/76ebdda
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/363787fa5cac2a8aa20638909210443278fa138e/5.3.4/platform010/9079c97
|
||||
@@ -164,12 +164,4 @@ EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GF
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
LUA_PATH="$LUA_BASE"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
LUA_LIB=" $LUA_PATH/lib/liblua.a"
|
||||
else
|
||||
LUA_LIB=" $LUA_PATH/lib/liblua_pic.a"
|
||||
fi
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
@@ -172,4 +172,4 @@ EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GF
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD
|
||||
@@ -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!"
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Pre-download packages with unreliable mirrors using fallback mirrors.
|
||||
Reads package info from folly's getdeps manifest files.
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import hashlib
|
||||
import subprocess
|
||||
import configparser
|
||||
|
||||
def sha256_file(path):
|
||||
"""Calculate SHA256 hash of a file."""
|
||||
h = hashlib.sha256()
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(65536), b''):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def parse_manifest(manifest_path):
|
||||
"""Parse a getdeps manifest file to extract download info."""
|
||||
config = configparser.ConfigParser()
|
||||
try:
|
||||
config.read(manifest_path)
|
||||
if 'download' in config:
|
||||
return {
|
||||
'url': config['download'].get('url', ''),
|
||||
'sha256': config['download'].get('sha256', ''),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
def get_fallback_mirrors(url):
|
||||
"""Get fallback mirror URLs for a given URL."""
|
||||
# Fallback mirror patterns for known unreliable hosts
|
||||
mirror_fallbacks = {
|
||||
"ftp.gnu.org/gnu/": [
|
||||
"https://mirrors.kernel.org/gnu/",
|
||||
"https://ftpmirror.gnu.org/gnu/",
|
||||
"https://ftp.gnu.org/gnu/",
|
||||
],
|
||||
"ftpmirror.gnu.org/gnu/": [
|
||||
"https://mirrors.kernel.org/gnu/",
|
||||
"https://ftpmirror.gnu.org/gnu/",
|
||||
"https://ftp.gnu.org/gnu/",
|
||||
],
|
||||
}
|
||||
|
||||
for pattern, mirrors in mirror_fallbacks.items():
|
||||
if pattern in url:
|
||||
# Extract the path after the pattern
|
||||
path_start = url.find(pattern) + len(pattern)
|
||||
path = url[path_start:]
|
||||
return [mirror + path for mirror in mirrors]
|
||||
return [url] # No fallback, use original
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
print(f"Usage: {sys.argv[0]} <download_dir> <cache_dir> <manifests_dir>")
|
||||
sys.exit(1)
|
||||
|
||||
download_dir, cache_dir, manifests_dir = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
|
||||
# Packages known to have unreliable mirrors
|
||||
packages_to_check = ["autoconf", "automake", "libtool"]
|
||||
|
||||
for package in packages_to_check:
|
||||
manifest_path = os.path.join(manifests_dir, package)
|
||||
if not os.path.exists(manifest_path):
|
||||
continue
|
||||
|
||||
info = parse_manifest(manifest_path)
|
||||
if not info or not info['url'] or not info['sha256']:
|
||||
continue
|
||||
|
||||
# Determine filename from URL
|
||||
url = info['url']
|
||||
expected_sha256 = info['sha256']
|
||||
url_filename = os.path.basename(url)
|
||||
|
||||
# getdeps uses format: {package}-{filename}
|
||||
filename = f"{package}-{url_filename}"
|
||||
filepath = os.path.join(download_dir, filename)
|
||||
cache_path = os.path.join(cache_dir, filename)
|
||||
|
||||
# Check if already valid
|
||||
if os.path.exists(filepath) and sha256_file(filepath) == expected_sha256:
|
||||
print(f" {filename}: OK (already downloaded)")
|
||||
continue
|
||||
|
||||
# Check cache
|
||||
if os.path.exists(cache_path) and sha256_file(cache_path) == expected_sha256:
|
||||
print(f" {filename}: OK (from cache)")
|
||||
subprocess.run(['cp', cache_path, filepath], check=True)
|
||||
continue
|
||||
|
||||
# Try fallback mirrors
|
||||
mirrors = get_fallback_mirrors(url)
|
||||
downloaded = False
|
||||
for mirror_url in mirrors:
|
||||
print(f" {filename}: trying {mirror_url}...")
|
||||
try:
|
||||
subprocess.run(['wget', '-q', '-O', filepath, mirror_url], check=True, timeout=120)
|
||||
if sha256_file(filepath) == expected_sha256:
|
||||
print(f" {filename}: OK (downloaded)")
|
||||
subprocess.run(['cp', filepath, cache_path], check=False)
|
||||
downloaded = True
|
||||
break
|
||||
else:
|
||||
os.remove(filepath)
|
||||
except Exception:
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
|
||||
if not downloaded:
|
||||
print(f" {filename}: WARNING - all mirrors failed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -101,6 +101,5 @@ get_lib_base benchmark LATEST platform010
|
||||
get_lib_base kernel-headers fb platform010
|
||||
get_lib_base binutils LATEST centos8-native
|
||||
get_lib_base valgrind LATEST platform010
|
||||
get_lib_base lua 5.3.4 platform010
|
||||
|
||||
git diff $OUTPUT
|
||||
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");
|
||||
|
||||
Vendored
+1
-1
@@ -644,7 +644,7 @@ using TypedHandle = SharedCache::TypedHandle;
|
||||
|
||||
TEST_P(CacheTest, SetCapacity) {
|
||||
if (IsHyperClock()) {
|
||||
// TODO: update test & code for limited supoort
|
||||
// TODO: update test & code for limited support
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"HyperClockCache doesn't support arbitrary capacity "
|
||||
"adjustments.");
|
||||
|
||||
Vendored
+355
-414
File diff suppressed because it is too large.
Load diff
Vendored
+135
-69
@@ -317,40 +317,89 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
// | acquire counter | release counter | hit bit | state marker |
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// For reading or updating counters in meta word.
|
||||
static constexpr uint8_t kCounterNumBits = 30;
|
||||
static constexpr uint64_t kCounterMask = (uint64_t{1} << kCounterNumBits) - 1;
|
||||
struct SlotMeta : public BitFields<uint64_t, SlotMeta> {
|
||||
// For reading or updating counters in meta word.
|
||||
static constexpr uint8_t kCounterNumBits = 30;
|
||||
// Number of times the a reference has been acquired (or attempted)
|
||||
// since last reset by eviction processing
|
||||
using AcquireCounter =
|
||||
UnsignedBitField<SlotMeta, kCounterNumBits, NoPrevBitField>;
|
||||
// Number of times the a reference has been released (or attempted)
|
||||
// since last reset by eviction processing
|
||||
using ReleaseCounter =
|
||||
UnsignedBitField<SlotMeta, kCounterNumBits, AcquireCounter>;
|
||||
// Metadata bit in support of secondary cache
|
||||
using HitFlag = BoolBitField<SlotMeta, ReleaseCounter>;
|
||||
// Occupied means any state other than empty
|
||||
using OccupiedFlag = BoolBitField<SlotMeta, HitFlag>;
|
||||
// Shareable means the entry is reference counted (visible or invisible)
|
||||
// (only set if also occupied)
|
||||
using ShareableFlag = BoolBitField<SlotMeta, OccupiedFlag>;
|
||||
// Visible is only set if also shareable (invisible can't be found by
|
||||
// Lookup)
|
||||
using VisibleFlag = BoolBitField<SlotMeta, ShareableFlag>;
|
||||
|
||||
static constexpr uint8_t kAcquireCounterShift = 0;
|
||||
static constexpr uint64_t kAcquireIncrement = uint64_t{1}
|
||||
<< kAcquireCounterShift;
|
||||
static constexpr uint8_t kReleaseCounterShift = kCounterNumBits;
|
||||
static constexpr uint64_t kReleaseIncrement = uint64_t{1}
|
||||
<< kReleaseCounterShift;
|
||||
// Convenience functions
|
||||
uint32_t GetAcquireCounter() const { return Get<AcquireCounter>(); }
|
||||
void SetAcquireCounter(uint32_t val) { Set<AcquireCounter>(val); }
|
||||
uint32_t GetReleaseCounter() const { return Get<ReleaseCounter>(); }
|
||||
void SetReleaseCounter(uint32_t val) { Set<ReleaseCounter>(val); }
|
||||
uint32_t GetRefcount() const {
|
||||
return Get<AcquireCounter>() - Get<ReleaseCounter>();
|
||||
}
|
||||
bool GetHit() const { return Get<HitFlag>(); }
|
||||
void SetHit(bool val) { Set<HitFlag>(val); }
|
||||
|
||||
// For setting the hit bit
|
||||
static constexpr uint8_t kHitBitShift = 2U * kCounterNumBits;
|
||||
static constexpr uint64_t kHitBitMask = uint64_t{1} << kHitBitShift;
|
||||
// Some distinct states for the various state flags
|
||||
bool IsEmpty() const {
|
||||
bool rv = !Get<OccupiedFlag>();
|
||||
if (rv) {
|
||||
assert(!Get<ShareableFlag>());
|
||||
assert(!Get<VisibleFlag>());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// For reading or updating the state marker in meta word
|
||||
static constexpr uint8_t kStateShift = kHitBitShift + 1;
|
||||
bool IsUnderConstruction() const {
|
||||
bool rv = Get<OccupiedFlag>() && !Get<ShareableFlag>();
|
||||
if (rv) {
|
||||
assert(!Get<VisibleFlag>());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
void SetUnderConstruction() {
|
||||
Set<OccupiedFlag>(true);
|
||||
Set<ShareableFlag>(false);
|
||||
Set<VisibleFlag>(false);
|
||||
}
|
||||
|
||||
// Bits contribution to state marker.
|
||||
// Occupied means any state other than empty
|
||||
static constexpr uint8_t kStateOccupiedBit = 0b100;
|
||||
// Shareable means the entry is reference counted (visible or invisible)
|
||||
// (only set if also occupied)
|
||||
static constexpr uint8_t kStateShareableBit = 0b010;
|
||||
// Visible is only set if also shareable
|
||||
static constexpr uint8_t kStateVisibleBit = 0b001;
|
||||
bool IsShareable() const { return Get<ShareableFlag>(); }
|
||||
bool IsInvisible() const {
|
||||
bool rv = Get<ShareableFlag>() && !Get<VisibleFlag>();
|
||||
if (rv) {
|
||||
assert(Get<OccupiedFlag>());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
void SetInvisible() {
|
||||
Set<OccupiedFlag>(true);
|
||||
Set<ShareableFlag>(true);
|
||||
Set<VisibleFlag>(false);
|
||||
}
|
||||
|
||||
// Complete state markers (not shifted into full word)
|
||||
static constexpr uint8_t kStateEmpty = 0b000;
|
||||
static constexpr uint8_t kStateConstruction = kStateOccupiedBit;
|
||||
static constexpr uint8_t kStateInvisible =
|
||||
kStateOccupiedBit | kStateShareableBit;
|
||||
static constexpr uint8_t kStateVisible =
|
||||
kStateOccupiedBit | kStateShareableBit | kStateVisibleBit;
|
||||
bool IsVisible() const {
|
||||
bool rv = Get<ShareableFlag>() && Get<VisibleFlag>();
|
||||
if (rv) {
|
||||
assert(Get<OccupiedFlag>());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
void SetVisible() {
|
||||
Set<OccupiedFlag>(true);
|
||||
Set<ShareableFlag>(true);
|
||||
Set<VisibleFlag>(true);
|
||||
}
|
||||
};
|
||||
|
||||
// Constants for initializing the countdown clock. (Countdown clock is only
|
||||
// in effect with zero refs, acquire counter == release counter, and in that
|
||||
@@ -364,7 +413,7 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
// TODO: make these coundown values tuning parameters for eviction?
|
||||
|
||||
// See above. Mutable for read reference counting.
|
||||
mutable AcqRelAtomic<uint64_t> meta{};
|
||||
mutable BitFieldsAtomic<SlotMeta> meta{};
|
||||
}; // struct ClockHandle
|
||||
|
||||
class BaseClockTable {
|
||||
@@ -431,9 +480,9 @@ class BaseClockTable {
|
||||
bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data) const;
|
||||
#ifndef NDEBUG
|
||||
// Acquire N references
|
||||
void TEST_RefN(ClockHandle& handle, size_t n);
|
||||
void TEST_RefN(ClockHandle& handle, uint32_t n);
|
||||
// Helper for TEST_ReleaseN
|
||||
void TEST_ReleaseNMinus1(ClockHandle* handle, size_t n);
|
||||
void TEST_ReleaseNMinus1(ClockHandle* handle, uint32_t n);
|
||||
#endif
|
||||
|
||||
private: // fns
|
||||
@@ -489,13 +538,13 @@ class BaseClockTable {
|
||||
// TODO: is this separation needed if we don't do background evictions?
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
AcqRelAtomic<size_t> occupancy_{};
|
||||
Atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
AcqRelAtomic<size_t> usage_{};
|
||||
Atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
AcqRelAtomic<size_t> standalone_usage_{};
|
||||
Atomic<size_t> standalone_usage_{};
|
||||
|
||||
// Maximum total charge of all elements stored in the table.
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
@@ -586,7 +635,7 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
bool GrowIfNeeded(size_t new_occupancy, InsertState& state);
|
||||
|
||||
HandleImpl* DoInsert(const ClockHandleBasicData& proto,
|
||||
uint64_t initial_countdown, bool take_ref,
|
||||
uint32_t initial_countdown, bool take_ref,
|
||||
InsertState& state);
|
||||
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
@@ -614,7 +663,7 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
}
|
||||
|
||||
// Release N references
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, uint32_t n);
|
||||
#endif
|
||||
|
||||
// The load factor p is a real number in (0, 1) such that at all
|
||||
@@ -775,6 +824,7 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
// chain--specifically the next entry in the chain.
|
||||
// * The end of a chain is given a special "end" marker and refers back
|
||||
// to the head of the chain.
|
||||
// These decorated pointers use the NextWithShift bit field struct below.
|
||||
//
|
||||
// Why do we need shift on each pointer? To make Lookup wait-free, we need
|
||||
// to be able to query a chain without missing anything, and preferably
|
||||
@@ -794,47 +844,63 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
// it is normal to see "under construction" entries on the chain, and it
|
||||
// is not safe to read their hashed key without either a read reference
|
||||
// on the entry or a rewrite lock on the chain.
|
||||
struct NextWithShift : public BitFields<uint64_t, NextWithShift> {
|
||||
// The "shift" associated with this decorated pointer (see description
|
||||
// above).
|
||||
using Shift = UnsignedBitField<NextWithShift, 6, NoPrevBitField>;
|
||||
// Marker for the end of a chain. Must also (a) point back to the head of
|
||||
// the chain (with end marker removed), and (b) set the LockedFlag
|
||||
// (below), so that attempting to lock an empty chain has no effect (not
|
||||
// needed, as the lock is only needed for removals).
|
||||
using EndFlag = BoolBitField<NextWithShift, Shift>;
|
||||
// Marker that some thread owning writes to the chain structure (except
|
||||
// for inserts), but only if not an "end" pointer. Also called the
|
||||
// "rewrite lock."
|
||||
using LockedFlag = BoolBitField<NextWithShift, EndFlag>;
|
||||
// The "next" associated with this decorated pointer, which is an index
|
||||
// into the table's array_ (see description above).
|
||||
using Next = UnsignedBitField<NextWithShift, 56, LockedFlag>;
|
||||
|
||||
// Marker in a "with_shift" head pointer for some thread owning writes
|
||||
// to the chain structure (except for inserts), but only if not an
|
||||
// "end" pointer. Also called the "rewrite lock."
|
||||
static constexpr uint64_t kHeadLocked = uint64_t{1} << 7;
|
||||
bool IsLocked() const { return Get<LockedFlag>(); }
|
||||
bool IsEnd() const {
|
||||
// End flag should imply locked flag
|
||||
assert(!Get<EndFlag>() || Get<LockedFlag>());
|
||||
return Get<EndFlag>();
|
||||
}
|
||||
bool IsLockedNotEnd() const {
|
||||
// NOTE: helping GCC to optimize this simpler code:
|
||||
// return IsLocked() && !IsEnd();
|
||||
constexpr U kEndFlag = U{1} << EndFlag::kBitOffset;
|
||||
constexpr U kLockedFlag = U{1} << LockedFlag::kBitOffset;
|
||||
return (underlying & (kEndFlag | kLockedFlag)) == kLockedFlag;
|
||||
}
|
||||
auto GetNext() const { return Get<Next>(); }
|
||||
auto GetShift() const { return Get<Shift>(); }
|
||||
|
||||
// Marker in a "with_shift" pointer for the end of a chain. Must also
|
||||
// point back to the head of the chain (with end marker removed).
|
||||
// Also includes the "locked" bit so that attempting to lock an empty
|
||||
// chain has no effect (not needed, as the lock is only needed for
|
||||
// removals).
|
||||
static constexpr uint64_t kNextEndFlags = (uint64_t{1} << 6) | kHeadLocked;
|
||||
static NextWithShift Make(size_t next, int shift) {
|
||||
return NextWithShift{}.With<Next>(next).With<Shift>(
|
||||
static_cast<uint8_t>(shift));
|
||||
}
|
||||
|
||||
static inline bool IsEnd(uint64_t next_with_shift) {
|
||||
// Assuming certain values never used, suffices to check this one bit
|
||||
constexpr auto kCheckBit = kNextEndFlags ^ kHeadLocked;
|
||||
return next_with_shift & kCheckBit;
|
||||
}
|
||||
|
||||
// Bottom bits to right shift away to get an array index from a
|
||||
// "with_shift" pointer.
|
||||
static constexpr int kNextShift = 8;
|
||||
|
||||
// A bit mask for the "shift" associated with each "with_shift" pointer.
|
||||
// Always bottommost bits.
|
||||
static constexpr int kShiftMask = 63;
|
||||
static NextWithShift MakeEnd(size_t next, int shift) {
|
||||
return Make(next, shift).With<EndFlag>(true).With<LockedFlag>(true);
|
||||
}
|
||||
};
|
||||
|
||||
// A marker for head_next_with_shift that indicates this HandleImpl is
|
||||
// heap allocated (standalone) rather than in the table.
|
||||
static constexpr uint64_t kStandaloneMarker = UINT64_MAX;
|
||||
static constexpr NextWithShift kStandaloneMarker{UINT64_MAX};
|
||||
|
||||
// A marker for head_next_with_shift indicating the head is not yet part
|
||||
// of the usable table, or for chain_next_with_shift indicating that the
|
||||
// entry is not present or is not yet part of a chain (must not be
|
||||
// "shareable" state).
|
||||
static constexpr uint64_t kUnusedMarker = 0;
|
||||
static constexpr NextWithShift kUnusedMarker{0};
|
||||
|
||||
// See above. The head pointer is logically independent of the rest of
|
||||
// the entry, including the chain next pointer.
|
||||
AcqRelAtomic<uint64_t> head_next_with_shift{kUnusedMarker};
|
||||
AcqRelAtomic<uint64_t> chain_next_with_shift{kUnusedMarker};
|
||||
BitFieldsAtomic<NextWithShift> head_next_with_shift{kUnusedMarker};
|
||||
BitFieldsAtomic<NextWithShift> chain_next_with_shift{kUnusedMarker};
|
||||
|
||||
// For supporting CreateStandalone and some fallback cases.
|
||||
inline bool IsStandalone() const {
|
||||
@@ -880,7 +946,7 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
bool GrowIfNeeded(size_t new_occupancy, InsertState& state);
|
||||
|
||||
HandleImpl* DoInsert(const ClockHandleBasicData& proto,
|
||||
uint64_t initial_countdown, bool take_ref,
|
||||
uint32_t initial_countdown, bool take_ref,
|
||||
InsertState& state);
|
||||
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
@@ -908,7 +974,7 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
}
|
||||
|
||||
// Release N references
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, uint32_t n);
|
||||
#endif
|
||||
|
||||
// Maximum ratio of number of occupied slots to number of usable slots. The
|
||||
@@ -990,7 +1056,7 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
// To maximize parallelization of Grow() operations, this field is only
|
||||
// updated opportunistically after Grow() operations and in DoInsert() where
|
||||
// it is found to be out-of-date. See CatchUpLengthInfoNoWait().
|
||||
AcqRelAtomic<uint64_t> length_info_;
|
||||
Atomic<uint64_t> length_info_;
|
||||
|
||||
// An already-computed version of the usable length times the max load
|
||||
// factor. Could be slightly out of date but GrowIfNeeded()/Grow() handle
|
||||
@@ -1113,8 +1179,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
return table_.TEST_MutableOccupancyLimit();
|
||||
}
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl* handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
void TEST_RefN(HandleImpl* handle, uint32_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, uint32_t n);
|
||||
#endif
|
||||
|
||||
private: // data
|
||||
|
||||
Vendored
+7
-7
@@ -1503,7 +1503,7 @@ TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
|
||||
// k1 promotion can fail with strict_capacit_limit=true, but Lookup still
|
||||
// k1 promotion can fail with strict_capacity_limit=true, but Lookup still
|
||||
// succeeds using a standalone handle
|
||||
handle1 = cache->Lookup(k1.AsSlice(), GetHelper(),
|
||||
/*context*/ this, Cache::Priority::LOW);
|
||||
@@ -1680,7 +1680,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
|
||||
// After Flush is successful, RocksDB will do the paranoid check for the new
|
||||
// SST file. Meta blocks are always cached in the block cache and they
|
||||
// will not be evicted. When block_2 is cache miss and read out, it is
|
||||
// inserted to the block cache. Thefore, block_1 is evicted from block
|
||||
// inserted to the block cache. Therefore, block_1 is evicted from block
|
||||
// cache and successfully inserted to the secondary cache. Here are 2
|
||||
// lookups in the secondary cache for block_1 and block_2.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
@@ -1721,7 +1721,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
|
||||
v = Get(Key(0));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
// This Get needs to access block_1, since block_1 is not in block cache
|
||||
// there is one econdary cache lookup. Then, block_1 is cached in the
|
||||
// there is one secondary cache lookup. Then, block_1 is cached in the
|
||||
// block cache.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 2u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 5u);
|
||||
@@ -1785,7 +1785,7 @@ TEST_P(DBSecondaryCacheTest, NoSecondaryCacheInsertion) {
|
||||
std::string v = Get(Key(0));
|
||||
ASSERT_EQ(1000, v.size());
|
||||
// Since the block cache is large enough, all the blocks are cached. we
|
||||
// do not need to lookup the seondary cache.
|
||||
// do not need to lookup the secondary cache.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
|
||||
|
||||
@@ -2150,7 +2150,7 @@ TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadBasic) {
|
||||
ASSERT_OK(Flush());
|
||||
Compact("a", "z");
|
||||
|
||||
// do th eread for all the key value pairs, so all the blocks should be in
|
||||
// do the read for all the key value pairs, so all the blocks should be in
|
||||
// cache
|
||||
uint32_t start_insert = cache->GetInsertCount();
|
||||
uint32_t start_lookup = cache->GetLookupcount();
|
||||
@@ -2464,7 +2464,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionBasic) {
|
||||
std::string v = Get(Key(0));
|
||||
ASSERT_EQ(1007, v.size());
|
||||
|
||||
// Check the data in first block. Cache miss, direclty read from SST file.
|
||||
// Check the data in first block. Cache miss, directly read from SST file.
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
|
||||
|
||||
@@ -2598,7 +2598,7 @@ TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) {
|
||||
}
|
||||
|
||||
// Two DB test. We create 2 DBs sharing the same block cache and secondary
|
||||
// cache. We diable the secondary cache option for DB2.
|
||||
// cache. We disable the secondary cache option for DB2.
|
||||
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) {
|
||||
if (IsHyperClock()) {
|
||||
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
|
||||
|
||||
+5
-1
@@ -8,7 +8,7 @@ DB_STRESS_CMD?=./db_stress
|
||||
include common.mk
|
||||
|
||||
CRASHTEST_MAKE=$(MAKE) -f crash_test.mk
|
||||
CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --cleanup_cmd='$(DB_CLEANUP_CMD)'
|
||||
CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --cleanup_cmd='$(DB_CLEANUP_CMD)' --destroy_db_initially=1
|
||||
|
||||
.PHONY: crash_test crash_test_with_atomic_flush crash_test_with_txn \
|
||||
crash_test_with_wc_txn crash_test_with_wp_txn crash_test_with_wup_txn \
|
||||
@@ -34,6 +34,7 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
|
||||
whitebox_crash_test_with_txn whitebox_crash_test_with_ts \
|
||||
whitebox_crash_test_with_optimistic_txn \
|
||||
whitebox_crash_test_with_tiered_storage \
|
||||
crash_test_db_cleanup \
|
||||
|
||||
crash_test: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
@@ -161,6 +162,9 @@ whitebox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --optimistic_txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
crash_test_db_cleanup: $(DB_STRESS_CMD)
|
||||
$(DB_STRESS_CMD) --delete_dir_and_exit=$(TEST_TMPDIR)
|
||||
|
||||
# Old names DEPRECATED
|
||||
crash_test_with_txn: crash_test_with_wc_txn
|
||||
whitebox_crash_test_with_txn: whitebox_crash_test_with_wc_txn
|
||||
|
||||
@@ -67,6 +67,16 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
min_blob_size_(mutable_cf_options->min_blob_size),
|
||||
blob_file_size_(mutable_cf_options->blob_file_size),
|
||||
blob_compression_type_(mutable_cf_options->blob_compression_type),
|
||||
// TODO: support most CompressionOptions with a new CF option
|
||||
// blob_compression_opts
|
||||
// TODO with schema change: support custom compression manager and options
|
||||
// such as max_compressed_bytes_per_kb
|
||||
// NOTE: returns nullptr for kNoCompression
|
||||
blob_compressor_(GetBuiltinV2CompressionManager()->GetCompressor(
|
||||
CompressionOptions{}, blob_compression_type_)),
|
||||
blob_compressor_wa_(blob_compressor_
|
||||
? blob_compressor_->ObtainWorkingArea()
|
||||
: Compressor::ManagedWorkingArea{}),
|
||||
prepopulate_blob_cache_(mutable_cf_options->prepopulate_blob_cache),
|
||||
file_options_(file_options),
|
||||
write_options_(write_options),
|
||||
@@ -113,7 +123,7 @@ Status BlobFileBuilder::Add(const Slice& key, const Slice& value,
|
||||
}
|
||||
|
||||
Slice blob = value;
|
||||
std::string compressed_blob;
|
||||
GrowableBuffer compressed_blob;
|
||||
|
||||
{
|
||||
const Status s = CompressBlobIfNeeded(&blob, &compressed_blob);
|
||||
@@ -254,36 +264,27 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
}
|
||||
|
||||
Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
Slice* blob, std::string* compressed_blob) const {
|
||||
Slice* blob, GrowableBuffer* compressed_blob) const {
|
||||
assert(blob);
|
||||
assert(compressed_blob);
|
||||
assert(compressed_blob->empty());
|
||||
assert(immutable_options_);
|
||||
|
||||
if (blob_compression_type_ == kNoCompression) {
|
||||
if (!blob_compressor_) {
|
||||
assert(blob_compression_type_ == kNoCompression);
|
||||
return Status::OK();
|
||||
}
|
||||
assert(blob_compression_type_ != kNoCompression);
|
||||
|
||||
// TODO: allow user CompressionOptions, including max_compressed_bytes_per_kb
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(blob_compression_type_, opts);
|
||||
// WART: always stored as compressed even when that increases the size.
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
blob_compression_type_);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
|
||||
bool success = false;
|
||||
|
||||
{
|
||||
StopWatch stop_watch(immutable_options_->clock, immutable_options_->stats,
|
||||
BLOB_DB_COMPRESSION_MICROS);
|
||||
success = OLD_CompressData(*blob, info, compression_format_version,
|
||||
compressed_blob);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return Status::Corruption("Error compressing blob");
|
||||
Status s;
|
||||
StopWatch stop_watch(immutable_options_->clock, immutable_options_->stats,
|
||||
BLOB_DB_COMPRESSION_MICROS);
|
||||
s = LegacyForceBuiltinCompression(*blob_compressor_, &blob_compressor_wa_,
|
||||
*blob, compressed_blob);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
*blob = Slice(*compressed_blob);
|
||||
|
||||
@@ -10,12 +10,14 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/advanced_compression.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "util/aligned_buffer.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -76,7 +78,8 @@ class BlobFileBuilder {
|
||||
private:
|
||||
bool IsBlobFileOpen() const;
|
||||
Status OpenBlobFileIfNeeded();
|
||||
Status CompressBlobIfNeeded(Slice* blob, std::string* compressed_blob) const;
|
||||
Status CompressBlobIfNeeded(Slice* blob,
|
||||
GrowableBuffer* compressed_blob) const;
|
||||
Status WriteBlobToFile(const Slice& key, const Slice& blob,
|
||||
uint64_t* blob_file_number, uint64_t* blob_offset);
|
||||
Status CloseBlobFile();
|
||||
@@ -91,6 +94,8 @@ class BlobFileBuilder {
|
||||
uint64_t min_blob_size_;
|
||||
uint64_t blob_file_size_;
|
||||
CompressionType blob_compression_type_;
|
||||
std::unique_ptr<Compressor> blob_compressor_;
|
||||
mutable Compressor::ManagedWorkingArea blob_compressor_wa_;
|
||||
PrepopulateBlobCache prepopulate_blob_cache_;
|
||||
const FileOptions* file_options_;
|
||||
const WriteOptions* write_options_;
|
||||
|
||||
@@ -457,11 +457,12 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue",
|
||||
[](void* arg) {
|
||||
bool* ret = static_cast<bool*>(arg);
|
||||
*ret = false;
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"LegacyForceBuiltinCompression:TamperWithStatus", [](void* arg) {
|
||||
Status* ret = static_cast<Status*>(arg);
|
||||
ASSERT_OK(*ret);
|
||||
*ret = Status::Corruption("Tampered result");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr char key[] = "1";
|
||||
@@ -469,7 +470,7 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
|
||||
std::string blob_index;
|
||||
|
||||
ASSERT_TRUE(builder.Add(key, value, &blob_index).IsCorruption());
|
||||
ASSERT_EQ(builder.Add(key, value, &blob_index).code(), Status::kCorruption);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
+37
-25
@@ -17,10 +17,10 @@
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/format.h"
|
||||
#include "table/multiget_context.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -69,9 +69,16 @@ Status BlobFileReader::Create(
|
||||
}
|
||||
}
|
||||
|
||||
blob_file_reader->reset(
|
||||
new BlobFileReader(std::move(file_reader), file_size, compression_type,
|
||||
immutable_options.clock, statistics));
|
||||
std::shared_ptr<Decompressor> decompressor;
|
||||
if (compression_type != kNoCompression) {
|
||||
// The blob format has always used compression format 2
|
||||
decompressor = GetBuiltinV2CompressionManager()->GetDecompressorOptimizeFor(
|
||||
compression_type);
|
||||
}
|
||||
|
||||
blob_file_reader->reset(new BlobFileReader(
|
||||
std::move(file_reader), file_size, compression_type,
|
||||
std::move(decompressor), immutable_options.clock, statistics));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -282,11 +289,13 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
|
||||
BlobFileReader::BlobFileReader(
|
||||
std::unique_ptr<RandomAccessFileReader>&& file_reader, uint64_t file_size,
|
||||
CompressionType compression_type, SystemClock* clock,
|
||||
CompressionType compression_type,
|
||||
std::shared_ptr<Decompressor> decompressor, SystemClock* clock,
|
||||
Statistics* statistics)
|
||||
: file_reader_(std::move(file_reader)),
|
||||
file_size_(file_size),
|
||||
compression_type_(compression_type),
|
||||
decompressor_(std::move(decompressor)),
|
||||
clock_(clock),
|
||||
statistics_(statistics) {
|
||||
assert(file_reader_);
|
||||
@@ -375,8 +384,9 @@ Status BlobFileReader::GetBlob(
|
||||
const Slice value_slice(record_slice.data() + adjustment, value_size);
|
||||
|
||||
{
|
||||
const Status s = UncompressBlobIfNeeded(
|
||||
value_slice, compression_type, allocator, clock_, statistics_, result);
|
||||
const Status s = UncompressBlobIfNeeded(value_slice, compression_type,
|
||||
decompressor_.get(), allocator,
|
||||
clock_, statistics_, result);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -524,9 +534,9 @@ void BlobFileReader::MultiGetBlob(
|
||||
|
||||
// Uncompress blob if needed
|
||||
Slice value_slice(record_slice.data() + adjustments[i], req->len);
|
||||
*req->status =
|
||||
UncompressBlobIfNeeded(value_slice, compression_type_, allocator,
|
||||
clock_, statistics_, &blob_reqs[i].second);
|
||||
*req->status = UncompressBlobIfNeeded(
|
||||
value_slice, compression_type_, decompressor_.get(), allocator, clock_,
|
||||
statistics_, &blob_reqs[i].second);
|
||||
if (req->status->ok()) {
|
||||
total_bytes += record_slice.size();
|
||||
}
|
||||
@@ -583,8 +593,8 @@ Status BlobFileReader::VerifyBlob(const Slice& record_slice,
|
||||
|
||||
Status BlobFileReader::UncompressBlobIfNeeded(
|
||||
const Slice& value_slice, CompressionType compression_type,
|
||||
MemoryAllocator* allocator, SystemClock* clock, Statistics* statistics,
|
||||
std::unique_ptr<BlobContents>* result) {
|
||||
Decompressor* decompressor, MemoryAllocator* allocator, SystemClock* clock,
|
||||
Statistics* statistics, std::unique_ptr<BlobContents>* result) {
|
||||
assert(result);
|
||||
|
||||
if (compression_type == kNoCompression) {
|
||||
@@ -593,31 +603,33 @@ Status BlobFileReader::UncompressBlobIfNeeded(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
UncompressionContext context(compression_type);
|
||||
UncompressionInfo info(context, UncompressionDict::GetEmptyDict(),
|
||||
compression_type);
|
||||
assert(decompressor);
|
||||
|
||||
size_t uncompressed_size = 0;
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
Decompressor::Args args;
|
||||
args.compression_type = compression_type;
|
||||
args.compressed_data = value_slice;
|
||||
|
||||
CacheAllocationPtr output;
|
||||
Status s = decompressor->ExtractUncompressedSize(args);
|
||||
if (!s.ok()) {
|
||||
return Status::Corruption(s.ToString());
|
||||
}
|
||||
|
||||
CacheAllocationPtr output = AllocateBlock(args.uncompressed_size, allocator);
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(blob_decompress_time);
|
||||
StopWatch stop_watch(clock, statistics, BLOB_DB_DECOMPRESSION_MICROS);
|
||||
output = OLD_UncompressData(info, value_slice.data(), value_slice.size(),
|
||||
&uncompressed_size, compression_format_version,
|
||||
allocator);
|
||||
s = decompressor->DecompressBlock(args, output.get());
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", &output);
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", &s);
|
||||
|
||||
if (!output) {
|
||||
return Status::Corruption("Unable to uncompress blob");
|
||||
if (!s.ok()) {
|
||||
return Status::Corruption(s.ToString());
|
||||
}
|
||||
|
||||
result->reset(new BlobContents(std::move(output), uncompressed_size));
|
||||
result->reset(new BlobContents(std::move(output), args.uncompressed_size));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "db/blob/blob_read_request.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "rocksdb/advanced_compression.h"
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "util/autovector.h"
|
||||
@@ -64,7 +65,8 @@ class BlobFileReader {
|
||||
private:
|
||||
BlobFileReader(std::unique_ptr<RandomAccessFileReader>&& file_reader,
|
||||
uint64_t file_size, CompressionType compression_type,
|
||||
SystemClock* clock, Statistics* statistics);
|
||||
std::shared_ptr<Decompressor> decompressor, SystemClock* clock,
|
||||
Statistics* statistics);
|
||||
|
||||
static Status OpenFile(const ImmutableOptions& immutable_options,
|
||||
const FileOptions& file_opts,
|
||||
@@ -96,6 +98,7 @@ class BlobFileReader {
|
||||
|
||||
static Status UncompressBlobIfNeeded(const Slice& value_slice,
|
||||
CompressionType compression_type,
|
||||
Decompressor* decompressor,
|
||||
MemoryAllocator* allocator,
|
||||
SystemClock* clock,
|
||||
Statistics* statistics,
|
||||
@@ -104,6 +107,7 @@ class BlobFileReader {
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader_;
|
||||
uint64_t file_size_;
|
||||
CompressionType compression_type_;
|
||||
std::shared_ptr<Decompressor> decompressor_;
|
||||
SystemClock* clock_;
|
||||
Statistics* statistics_;
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
|
||||
ASSERT_OK(blob_log_writer.WriteHeader(WriteOptions(), header));
|
||||
|
||||
std::vector<std::string> compressed_blobs(num);
|
||||
std::vector<GrowableBuffer> compressed_blobs(num);
|
||||
std::vector<Slice> blobs_to_write(num);
|
||||
if (kNoCompression == compression) {
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
@@ -73,16 +73,13 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
blob_sizes[i] = blobs[i].size();
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
auto compressor =
|
||||
GetBuiltinV2CompressionManager()->GetCompressor({}, compression);
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
ASSERT_TRUE(OLD_CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
ASSERT_OK(LegacyForceBuiltinCompression(*compressor,
|
||||
/*working_area=*/nullptr,
|
||||
blobs[i], &compressed_blobs[i]));
|
||||
blobs_to_write[i] = compressed_blobs[i];
|
||||
blob_sizes[i] = compressed_blobs[i].size();
|
||||
}
|
||||
@@ -809,11 +806,10 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", [](void* arg) {
|
||||
CacheAllocationPtr* const output =
|
||||
static_cast<CacheAllocationPtr*>(arg);
|
||||
assert(output);
|
||||
auto* result = static_cast<Status*>(arg);
|
||||
assert(result);
|
||||
|
||||
output->reset();
|
||||
*result = Status::Corruption("Injected result");
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -824,11 +820,12 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
std::unique_ptr<BlobContents> value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kSnappyCompression, prefetch_buffer, allocator,
|
||||
&value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kSnappyCompression, prefetch_buffer, allocator,
|
||||
&value, &bytes_read)
|
||||
.code(),
|
||||
Status::Code::kCorruption);
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
|
||||
ASSERT_OK(blob_log_writer.WriteHeader(WriteOptions(), header));
|
||||
|
||||
std::vector<std::string> compressed_blobs(num);
|
||||
std::vector<GrowableBuffer> compressed_blobs(num);
|
||||
std::vector<Slice> blobs_to_write(num);
|
||||
if (kNoCompression == compression) {
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
@@ -75,16 +75,13 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
blob_sizes[i] = blobs[i].size();
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
auto compressor =
|
||||
GetBuiltinV2CompressionManager()->GetCompressor({}, compression);
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
ASSERT_TRUE(OLD_CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
ASSERT_OK(LegacyForceBuiltinCompression(*compressor,
|
||||
/*working_area=*/nullptr,
|
||||
blobs[i], &compressed_blobs[i]));
|
||||
blobs_to_write[i] = compressed_blobs[i];
|
||||
blob_sizes[i] = compressed_blobs[i].size();
|
||||
}
|
||||
|
||||
@@ -56,6 +56,18 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
return tboptions.moptions.table_factory->NewTableBuilder(tboptions, file);
|
||||
}
|
||||
|
||||
void ExtractTimestampFromTableProperties(const TableProperties& tp,
|
||||
FileMetaData* meta) {
|
||||
auto min_ts_iter = tp.user_collected_properties.find("rocksdb.timestamp_min");
|
||||
if (min_ts_iter != tp.user_collected_properties.end()) {
|
||||
meta->min_timestamp = min_ts_iter->second;
|
||||
}
|
||||
auto max_ts_iter = tp.user_collected_properties.find("rocksdb.timestamp_max");
|
||||
if (max_ts_iter != tp.user_collected_properties.end()) {
|
||||
meta->max_timestamp = max_ts_iter->second;
|
||||
}
|
||||
}
|
||||
|
||||
Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
|
||||
@@ -355,6 +367,7 @@ Status BuildTable(
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder
|
||||
->GetTableProperties(); // refresh now that builder is finished
|
||||
ExtractTimestampFromTableProperties(tp, meta);
|
||||
if (memtable_payload_bytes != nullptr &&
|
||||
memtable_garbage_bytes != nullptr) {
|
||||
const CompactionIterationStats& ci_stats = c_iter.iter_stats();
|
||||
|
||||
@@ -41,6 +41,13 @@ class BlobFileCompletionCallback;
|
||||
TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
WritableFileWriter* file);
|
||||
|
||||
// Extract min/max timestamps from table properties and populate FileMetaData.
|
||||
// This is used by both flush (BuildTable) and compaction (CompactionOutputs)
|
||||
// to populate timestamp range in FileMetaData from the TimestampTableProperties
|
||||
// collector output.
|
||||
void ExtractTimestampFromTableProperties(const TableProperties& tp,
|
||||
FileMetaData* meta);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
// will be named according to number specified in meta. On success, the rest of
|
||||
// *meta will be filled with metadata about the generated table.
|
||||
|
||||
+648
@@ -722,6 +722,88 @@ static void LoadAndCheckLatestOptions(const char* db_name, rocksdb_env_t* env,
|
||||
num_column_families);
|
||||
}
|
||||
|
||||
// Global state for tracking remote compaction calls
|
||||
typedef struct {
|
||||
int schedule_called;
|
||||
int wait_called;
|
||||
int cancel_called;
|
||||
char last_scheduled_job_id[256];
|
||||
char last_db_name[256];
|
||||
} RemoteCompactionState;
|
||||
|
||||
// Schedule callback - gets called when compaction is scheduled
|
||||
static rocksdb_compactionservice_scheduleresponse_t* RemoteCompactionSchedule(
|
||||
void* state, const rocksdb_compactionservice_jobinfo_t* info,
|
||||
const char* input, size_t input_len) {
|
||||
(void)input;
|
||||
(void)input_len;
|
||||
RemoteCompactionState* rcs = (RemoteCompactionState*)state;
|
||||
rcs->schedule_called++;
|
||||
|
||||
// Extract job info
|
||||
size_t db_name_len;
|
||||
const char* db_name =
|
||||
rocksdb_compactionservice_jobinfo_t_get_db_name(info, &db_name_len);
|
||||
memcpy(rcs->last_db_name, db_name, db_name_len);
|
||||
rcs->last_db_name[db_name_len] = '\0';
|
||||
|
||||
// Generate a job ID
|
||||
snprintf(rcs->last_scheduled_job_id, sizeof(rcs->last_scheduled_job_id),
|
||||
"job-%d", rcs->schedule_called);
|
||||
|
||||
// Create response with success status
|
||||
char* err = NULL;
|
||||
rocksdb_compactionservice_scheduleresponse_t* response =
|
||||
rocksdb_compactionservice_scheduleresponse_create(
|
||||
rcs->last_scheduled_job_id,
|
||||
rocksdb_compactionservice_jobstatus_success, &err);
|
||||
if (err) {
|
||||
free(err);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// Wait callback - simulates waiting for remote compaction to complete
|
||||
static int RemoteCompactionWait(void* state, const char* scheduled_job_id,
|
||||
char** result, size_t* result_len) {
|
||||
RemoteCompactionState* rcs = (RemoteCompactionState*)state;
|
||||
rcs->wait_called++;
|
||||
|
||||
if (strcmp(scheduled_job_id, rcs->last_scheduled_job_id) != 0) {
|
||||
return rocksdb_compactionservice_jobstatus_failure;
|
||||
}
|
||||
|
||||
// For testing purposes, return kUseLocal to cause RocksDB to fall back to
|
||||
// local compaction. This tests the callback mechanism without needing a fully
|
||||
// serialized result. In a real scenario, this would communicate with a remote
|
||||
// worker that calls rocksdb_open_and_compact() and returns a properly
|
||||
// serialized CompactionServiceResult
|
||||
*result = NULL;
|
||||
*result_len = 0;
|
||||
|
||||
return rocksdb_compactionservice_jobstatus_use_local;
|
||||
}
|
||||
|
||||
// Cancel callback - cancels pending jobs
|
||||
static void RemoteCompactionCancel(void* state) {
|
||||
RemoteCompactionState* rcs = (RemoteCompactionState*)state;
|
||||
rcs->cancel_called++;
|
||||
}
|
||||
|
||||
// Destructor callback
|
||||
static void RemoteCompactionDestroy(void* state) { (void)state; }
|
||||
|
||||
// NULL schedule callback for testing failure handling
|
||||
static rocksdb_compactionservice_scheduleresponse_t* NullSchedule(
|
||||
void* state, const rocksdb_compactionservice_jobinfo_t* info,
|
||||
const char* input, size_t input_len) {
|
||||
(void)state;
|
||||
(void)info;
|
||||
(void)input;
|
||||
(void)input_len;
|
||||
return NULL; // Return NULL to simulate failure
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
@@ -1255,6 +1337,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 +1566,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 +1688,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 +2152,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 +2277,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 +3816,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);
|
||||
@@ -4207,6 +4565,296 @@ int main(int argc, char** argv) {
|
||||
rocksdb_cache_destroy(lru);
|
||||
}
|
||||
|
||||
StartPhase("remote_compaction_service");
|
||||
{
|
||||
RemoteCompactionState remote_state = {0, 0, 0, "", ""};
|
||||
|
||||
// Create compaction service
|
||||
rocksdb_compactionservice_t* service = rocksdb_compactionservice_create(
|
||||
&remote_state, // state
|
||||
RemoteCompactionDestroy, // destructor
|
||||
RemoteCompactionSchedule, // schedule callback
|
||||
"TestRemoteCompaction", // name
|
||||
RemoteCompactionWait, // wait callback
|
||||
RemoteCompactionCancel, // cancel_awaiting_jobs
|
||||
NULL); // on_installation
|
||||
|
||||
// Create options with remote compaction
|
||||
rocksdb_options_t* remote_options = rocksdb_options_create();
|
||||
rocksdb_options_set_create_if_missing(remote_options, 1);
|
||||
rocksdb_options_set_level0_file_num_compaction_trigger(remote_options, 2);
|
||||
rocksdb_options_set_write_buffer_size(remote_options,
|
||||
64 * 1024); // 64KB buffer
|
||||
rocksdb_options_set_max_bytes_for_level_base(remote_options,
|
||||
256 * 1024); // 256KB
|
||||
rocksdb_options_set_target_file_size_base(
|
||||
remote_options, 64 * 1024); // 64KB target file size
|
||||
// Disable automatic compactions to test manual compaction only
|
||||
rocksdb_options_set_disable_auto_compactions(remote_options, 1);
|
||||
rocksdb_options_set_compaction_service(remote_options, service);
|
||||
|
||||
// Destroy old DB and create new one
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(remote_options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
db = rocksdb_open(remote_options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// Create multiple SST files to trigger compaction
|
||||
rocksdb_flushoptions_t* flush_opts = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_opts, 1);
|
||||
|
||||
// Write and flush multiple times to create multiple L0 files
|
||||
// Write more data with larger values to ensure files are substantial
|
||||
for (int batch = 0; batch < 5; batch++) {
|
||||
for (int i = 0; i < 200; i++) {
|
||||
char key[20], val[1000];
|
||||
snprintf(key, sizeof(key), "key%d_%d", batch, i);
|
||||
// Fill value with repeated data to make it larger
|
||||
memset(val, 'a' + (batch % 26), sizeof(val) - 1);
|
||||
val[sizeof(val) - 1] = '\0';
|
||||
rocksdb_put(db, woptions, key, strlen(key), val, strlen(val), &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
rocksdb_flush(db, flush_opts, &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
rocksdb_flushoptions_destroy(flush_opts);
|
||||
|
||||
// Trigger manual compaction to invoke remote compaction service
|
||||
rocksdb_compact_range(db, NULL, 0, NULL, 0);
|
||||
|
||||
rocksdb_wait_for_compact_options_t* wco =
|
||||
rocksdb_wait_for_compact_options_create();
|
||||
rocksdb_wait_for_compact(db, wco, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_wait_for_compact_options_destroy(wco);
|
||||
|
||||
// Verify that callbacks were actually called
|
||||
CheckCondition(remote_state.schedule_called > 0);
|
||||
CheckCondition(remote_state.wait_called > 0);
|
||||
CheckCondition(strlen(remote_state.last_db_name) > 0);
|
||||
CheckCondition(strstr(remote_state.last_db_name, "rocksdb_c_test") != NULL);
|
||||
|
||||
// Verify data is still accessible after remote compaction
|
||||
// Just check a few keys to verify data integrity
|
||||
for (int batch = 0; batch < 5; batch++) {
|
||||
char key[20];
|
||||
snprintf(key, sizeof(key), "key%d_0", batch);
|
||||
size_t vallen;
|
||||
char* val = rocksdb_get(db, roptions, key, strlen(key), &vallen, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(val != NULL);
|
||||
CheckCondition(vallen == 999); // strlen of 1000-byte string
|
||||
free(val);
|
||||
}
|
||||
|
||||
// Test cancellation API directly
|
||||
RemoteCompactionCancel(&remote_state);
|
||||
CheckCondition(remote_state.cancel_called > 0);
|
||||
|
||||
// Cleanup
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(remote_options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_options_destroy(remote_options);
|
||||
|
||||
// Reopen DB with original options for subsequent tests
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
|
||||
StartPhase("remote_compaction_scheduleresponse");
|
||||
{
|
||||
// Test scheduleresponse creation and getters
|
||||
rocksdb_compactionservice_scheduleresponse_t* response;
|
||||
|
||||
// Test success response
|
||||
err = NULL;
|
||||
response = rocksdb_compactionservice_scheduleresponse_create(
|
||||
"test-job-123", rocksdb_compactionservice_jobstatus_success, &err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(response != NULL);
|
||||
CheckCondition(
|
||||
rocksdb_compactionservice_scheduleresponse_getstatus(response) ==
|
||||
rocksdb_compactionservice_jobstatus_success);
|
||||
|
||||
size_t job_id_len;
|
||||
const char* job_id =
|
||||
rocksdb_compactionservice_scheduleresponse_get_scheduled_job_id(
|
||||
response, &job_id_len);
|
||||
CheckCondition(job_id_len == strlen("test-job-123"));
|
||||
CheckCondition(memcmp(job_id, "test-job-123", job_id_len) == 0);
|
||||
rocksdb_compactionservice_scheduleresponse_t_destroy(response);
|
||||
|
||||
// Test failure response
|
||||
response = rocksdb_compactionservice_scheduleresponse_create_with_status(
|
||||
rocksdb_compactionservice_jobstatus_failure, &err);
|
||||
CheckCondition(response != NULL);
|
||||
CheckCondition(
|
||||
rocksdb_compactionservice_scheduleresponse_getstatus(response) ==
|
||||
rocksdb_compactionservice_jobstatus_failure);
|
||||
rocksdb_compactionservice_scheduleresponse_t_destroy(response);
|
||||
|
||||
response = rocksdb_compactionservice_scheduleresponse_create_with_status(
|
||||
999, &err);
|
||||
CheckCondition(response == NULL); // Invalid status
|
||||
if (err) {
|
||||
Free(&err);
|
||||
}
|
||||
}
|
||||
|
||||
StartPhase("remote_compaction_options_override");
|
||||
{
|
||||
// Test CompactionServiceOptionsOverride API
|
||||
rocksdb_compaction_service_options_override_t* override_opts =
|
||||
rocksdb_compaction_service_options_override_create();
|
||||
CheckCondition(override_opts != NULL);
|
||||
|
||||
// Set up override options
|
||||
rocksdb_compaction_service_options_override_set_env(override_opts, env);
|
||||
rocksdb_compaction_service_options_override_set_comparator(override_opts,
|
||||
cmp);
|
||||
|
||||
// Test file checksum gen factory
|
||||
rocksdb_file_checksum_gen_factory_t* checksum_factory =
|
||||
rocksdb_file_checksum_gen_crc32c_factory_create();
|
||||
CheckCondition(checksum_factory != NULL);
|
||||
rocksdb_compaction_service_options_override_set_file_checksum_gen_factory(
|
||||
override_opts, checksum_factory);
|
||||
|
||||
// Test SST partitioner factory
|
||||
rocksdb_sst_partitioner_factory_t* partitioner_factory =
|
||||
rocksdb_sst_partitioner_fixed_prefix_factory_create(4);
|
||||
CheckCondition(partitioner_factory != NULL);
|
||||
rocksdb_compaction_service_options_override_set_sst_partitioner_factory(
|
||||
override_opts, partitioner_factory);
|
||||
|
||||
// Test merge operator
|
||||
rocksdb_compaction_service_options_override_set_merge_operator(
|
||||
override_opts, NULL);
|
||||
|
||||
// Test compaction filter
|
||||
rocksdb_compaction_service_options_override_set_compaction_filter(
|
||||
override_opts, NULL);
|
||||
|
||||
// Test prefix extractor
|
||||
rocksdb_compaction_service_options_override_set_prefix_extractor(
|
||||
override_opts, NULL);
|
||||
|
||||
// Test table factory - block based
|
||||
rocksdb_block_based_table_options_t* table_opts =
|
||||
rocksdb_block_based_options_create();
|
||||
rocksdb_compaction_service_options_override_set_block_based_table_factory(
|
||||
override_opts, table_opts);
|
||||
rocksdb_block_based_options_destroy(table_opts);
|
||||
|
||||
// Test statistics via options
|
||||
rocksdb_options_t* stats_opts = rocksdb_options_create();
|
||||
rocksdb_options_enable_statistics(stats_opts);
|
||||
rocksdb_compaction_service_options_override_set_statistics(override_opts,
|
||||
stats_opts);
|
||||
rocksdb_options_destroy(stats_opts);
|
||||
|
||||
// Test info log
|
||||
rocksdb_logger_t* logger =
|
||||
rocksdb_logger_create_stderr_logger(1, "test_prefix");
|
||||
rocksdb_compaction_service_options_override_set_info_log(override_opts,
|
||||
logger);
|
||||
rocksdb_logger_destroy(logger);
|
||||
|
||||
// Test options map
|
||||
rocksdb_compaction_service_options_override_set_option(
|
||||
override_opts, "max_bytes_for_level_base", "67108864");
|
||||
|
||||
// Cleanup
|
||||
rocksdb_file_checksum_gen_factory_destroy(checksum_factory);
|
||||
rocksdb_sst_partitioner_factory_destroy(partitioner_factory);
|
||||
rocksdb_compaction_service_options_override_destroy(override_opts);
|
||||
}
|
||||
|
||||
StartPhase("factory_options_on_regular_options");
|
||||
{
|
||||
// Test that the new factory types work with regular rocksdb_options_t
|
||||
rocksdb_options_t* test_opts = rocksdb_options_create();
|
||||
|
||||
// Test file checksum gen factory on regular options
|
||||
rocksdb_file_checksum_gen_factory_t* checksum_factory =
|
||||
rocksdb_file_checksum_gen_crc32c_factory_create();
|
||||
CheckCondition(checksum_factory != NULL);
|
||||
rocksdb_options_set_file_checksum_gen_factory(test_opts, checksum_factory);
|
||||
|
||||
// Test SST partitioner factory on regular options
|
||||
rocksdb_sst_partitioner_factory_t* partitioner_factory =
|
||||
rocksdb_sst_partitioner_fixed_prefix_factory_create(8);
|
||||
CheckCondition(partitioner_factory != NULL);
|
||||
rocksdb_options_set_sst_partitioner_factory(test_opts, partitioner_factory);
|
||||
|
||||
// Cleanup
|
||||
rocksdb_file_checksum_gen_factory_destroy(checksum_factory);
|
||||
rocksdb_sst_partitioner_factory_destroy(partitioner_factory);
|
||||
rocksdb_options_destroy(test_opts);
|
||||
}
|
||||
|
||||
StartPhase("remote_compaction_null_callback_handling");
|
||||
{
|
||||
// Test that NULL callback returns are handled gracefully
|
||||
// This simulates a failure in the remote compaction service
|
||||
rocksdb_compactionservice_t* null_service =
|
||||
rocksdb_compactionservice_create(NULL, NULL, NullSchedule,
|
||||
"NullTestService", NULL, NULL, NULL);
|
||||
|
||||
rocksdb_options_t* null_opts = rocksdb_options_create();
|
||||
rocksdb_options_set_create_if_missing(null_opts, 1);
|
||||
rocksdb_options_set_compaction_service(null_opts, null_service);
|
||||
|
||||
const char* null_db = "rocksdb_c_test_null_service";
|
||||
|
||||
rocksdb_t* null_db_handle = rocksdb_open(null_opts, null_db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// Write data and trigger compaction
|
||||
for (int i = 0; i < 100; i++) {
|
||||
char key[20], val[50];
|
||||
snprintf(key, sizeof(key), "key%d", i);
|
||||
snprintf(val, sizeof(val), "val%d", i);
|
||||
rocksdb_put(null_db_handle, woptions, key, strlen(key), val, strlen(val),
|
||||
&err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
|
||||
// This should fall back to local compaction (not crash)
|
||||
rocksdb_compact_range(null_db_handle, NULL, 0, NULL, 0);
|
||||
|
||||
// Data should still be readable
|
||||
CheckGet(null_db_handle, roptions, "key50", "val50");
|
||||
|
||||
rocksdb_close(null_db_handle);
|
||||
rocksdb_destroy_db(null_opts, null_db, &err);
|
||||
rocksdb_options_destroy(null_opts);
|
||||
}
|
||||
|
||||
StartPhase("remote_compaction_canceled_flag");
|
||||
{
|
||||
// Test atomic cancellation flag API
|
||||
unsigned char* canceled = rocksdb_open_and_compact_canceled_create();
|
||||
CheckCondition(canceled != NULL);
|
||||
|
||||
// Set cancellation
|
||||
rocksdb_open_and_compact_canceled_set(canceled, 1);
|
||||
|
||||
// Use with OpenAndCompactOptions
|
||||
rocksdb_open_and_compact_options_t* oac_opts =
|
||||
rocksdb_open_and_compact_options_create();
|
||||
rocksdb_open_and_compact_options_set_canceled(oac_opts, canceled);
|
||||
rocksdb_open_and_compact_options_set_allow_resumption(oac_opts, 1);
|
||||
|
||||
// Cleanup
|
||||
rocksdb_open_and_compact_options_destroy(oac_opts);
|
||||
rocksdb_open_and_compact_canceled_destroy(canceled);
|
||||
}
|
||||
|
||||
StartPhase("sst_file_manager");
|
||||
{
|
||||
rocksdb_sst_file_manager_t* sst_file_manager;
|
||||
|
||||
+2
-2
@@ -1248,7 +1248,7 @@ Compaction* ColumnFamilyData::PickCompaction(
|
||||
auto* result = compaction_picker_->PickCompaction(
|
||||
GetName(), mutable_options, mutable_db_options, existing_snapshots,
|
||||
snapshot_checker, current_->storage_info(), log_buffer,
|
||||
require_max_output_level);
|
||||
GetFullHistoryTsLow(), require_max_output_level);
|
||||
if (result != nullptr) {
|
||||
result->FinalizeInputInfo(current_);
|
||||
}
|
||||
@@ -1336,7 +1336,7 @@ Compaction* ColumnFamilyData::CompactRange(
|
||||
GetName(), mutable_cf_options, mutable_db_options,
|
||||
current_->storage_info(), input_level, output_level,
|
||||
compact_range_options, begin, end, compaction_end, conflict,
|
||||
max_file_num_to_ignore, trim_ts);
|
||||
max_file_num_to_ignore, trim_ts, GetFullHistoryTsLow());
|
||||
if (result != nullptr) {
|
||||
result->FinalizeInputInfo(current_);
|
||||
}
|
||||
|
||||
+229
-2
@@ -433,8 +433,9 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
|
||||
ROCKSDB_NAMESPACE::TablePropertiesCollection all_tables_props;
|
||||
ASSERT_OK(db->GetPropertiesOfAllTables(&all_tables_props));
|
||||
for (const auto& name_and_table_props : all_tables_props) {
|
||||
ASSERT_EQ(CompressionTypeToString(CompressionType::kZlibCompression),
|
||||
name_and_table_props.second->compression_name);
|
||||
// As of format_version 7, more elaborate information is encoded into the
|
||||
// compression_name property
|
||||
ASSERT_EQ("BuiltinV2;02;", name_and_table_props.second->compression_name);
|
||||
}
|
||||
delete db;
|
||||
}
|
||||
@@ -534,6 +535,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];
|
||||
}
|
||||
|
||||
@@ -1310,14 +1310,14 @@ void CompactionIterator::PrepareOutput() {
|
||||
validity_info_.rep);
|
||||
assert(false);
|
||||
}
|
||||
ikey_.sequence = 0;
|
||||
last_key_seq_zeroed_ = true;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput:ZeroingSeq",
|
||||
&ikey_);
|
||||
|
||||
bool zeroed_seqno = false;
|
||||
if (!timestamp_size_) {
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
zeroed_seqno = true;
|
||||
} else if (full_history_ts_low_ && cmp_with_history_ts_low_ < 0) {
|
||||
// We can also zero out timestamp for better compression.
|
||||
// For UDT, the seqno and timestamp could only be zeroed out after the
|
||||
// key is below history_ts_low_.
|
||||
// For the same user key (excluding timestamp), the timestamp-based
|
||||
// history can be collapsed to save some space if the timestamp is
|
||||
// older than *full_history_ts_low_.
|
||||
@@ -1325,6 +1325,14 @@ void CompactionIterator::PrepareOutput() {
|
||||
const Slice ts_slice = kTsMin;
|
||||
ikey_.SetTimestamp(ts_slice);
|
||||
current_key_.UpdateInternalKey(0, ikey_.type, &ts_slice);
|
||||
zeroed_seqno = true;
|
||||
}
|
||||
|
||||
if (zeroed_seqno) {
|
||||
ikey_.sequence = 0;
|
||||
last_key_seq_zeroed_ = true;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput:ZeroingSeq",
|
||||
&ikey_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+123
-58
@@ -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)) {
|
||||
@@ -2402,6 +2438,32 @@ bool CompactionJob::UpdateInternalStatsFromInputFiles(
|
||||
bool has_error = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
const auto& input_table_properties = compaction->GetInputTableProperties();
|
||||
|
||||
// Check all input files for old block-based SST format_version. Why? Old
|
||||
// block-based SST files from roughly version 5.0 to 5.18 could produce
|
||||
// inaccurate num_entries counts due to the evolution of its handling along
|
||||
// with num_range_deletions. We have to disable some paranoid checks when
|
||||
// compacting files from such an old release. However, we don't have great
|
||||
// information to identify those files, so we heuristically over-approximate
|
||||
// that set of files using
|
||||
// (a) format_version < 5, which will be true for any files from RocksDB <
|
||||
// 6.6.0 and should not be true for any recent production files
|
||||
// (b) to avoid including non-block-based SST files (which still use older
|
||||
// format_version markers, and do not support DeleteRange), we also require
|
||||
// the presence of the user property "rocksdb.block.based.table.index.type",
|
||||
// which was added in RocksDB 2.8 and is always present in block-based tables.
|
||||
for (const auto& tp_pair : input_table_properties) {
|
||||
if (tp_pair.second && tp_pair.second->format_version < 5) {
|
||||
// Check for block-based table by looking for its index type property
|
||||
const auto& user_props = tp_pair.second->user_collected_properties;
|
||||
if (user_props.find(BlockBasedTablePropertyNames::kIndexType) !=
|
||||
user_props.end()) {
|
||||
job_stats_->has_accurate_num_input_records = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
@@ -2646,7 +2708,10 @@ Status CompactionJob::ReadTablePropertiesDirectly(
|
||||
std::shared_ptr<const TableProperties>* tp) {
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
std::string file_name = GetTableFileName(file_meta->fd.GetNumber());
|
||||
Status s = ioptions.fs->NewRandomAccessFile(file_name, file_options_, &file,
|
||||
FileOptions fopts = file_options_;
|
||||
fopts.file_checksum = file_meta->file_checksum;
|
||||
fopts.file_checksum_func_name = file_meta->file_checksum_func_name;
|
||||
Status s = ioptions.fs->NewRandomAccessFile(file_name, fopts, &file,
|
||||
nullptr /* dbg */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -250,6 +250,10 @@ class CompactionJob {
|
||||
// @param num_input_range_del if non-null, will be set to the number of range
|
||||
// deletion entries in this compaction input.
|
||||
//
|
||||
// If any input file has potentially unreliable num_entries count (old SST
|
||||
// files - details in implementation),
|
||||
// job_stats_->has_accurate_num_input_records is set to false.
|
||||
//
|
||||
// Returns true iff internal_stats_.output_level_stats.num_input_records and
|
||||
// num_input_range_del are calculated successfully.
|
||||
//
|
||||
@@ -430,7 +434,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 +549,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
|
||||
|
||||
|
||||
@@ -49,8 +49,10 @@ Status CompactionOutputs::Finish(
|
||||
meta->fd.file_size = current_bytes;
|
||||
meta->tail_size = builder_->GetTailSize();
|
||||
meta->marked_for_compaction = builder_->NeedCompact();
|
||||
meta->user_defined_timestamps_persisted = static_cast<bool>(
|
||||
builder_->GetTableProperties().user_defined_timestamps_persisted);
|
||||
const TableProperties& tp = builder_->GetTableProperties();
|
||||
meta->user_defined_timestamps_persisted =
|
||||
static_cast<bool>(tp.user_defined_timestamps_persisted);
|
||||
ExtractTimestampFromTableProperties(tp, meta);
|
||||
}
|
||||
current_output().finished = true;
|
||||
stats_.bytes_written += current_bytes;
|
||||
@@ -278,7 +280,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 +366,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 +377,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)) {
|
||||
@@ -610,7 +611,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
int input_level, int output_level,
|
||||
const CompactRangeOptions& compact_range_options, const InternalKey* begin,
|
||||
const InternalKey* end, InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore, const std::string& trim_ts) {
|
||||
uint64_t max_file_num_to_ignore, const std::string& trim_ts,
|
||||
const std::string& full_history_ts_low) {
|
||||
// CompactionPickerFIFO has its own implementation of compact range
|
||||
assert(ioptions_.compaction_style != kCompactionStyleFIFO);
|
||||
|
||||
@@ -689,7 +691,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
compact_range_options.blob_garbage_collection_age_cutoff);
|
||||
|
||||
RegisterCompaction(c);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options,
|
||||
full_history_ts_low);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -886,7 +889,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
// being compacted). Since we just changed compaction score, we recalculate it
|
||||
// here
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options,
|
||||
full_history_ts_low);
|
||||
|
||||
return compaction;
|
||||
}
|
||||
@@ -1231,7 +1235,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 +1246,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
|
||||
|
||||
@@ -65,7 +65,8 @@ class CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer, bool require_max_output_level) = 0;
|
||||
LogBuffer* log_buffer, const std::string& full_history_ts_low,
|
||||
bool require_max_output_level = false) = 0;
|
||||
|
||||
// The returned Compaction might not include the whole requested range.
|
||||
// In that case, compaction_end will be set to the next key that needs
|
||||
@@ -82,7 +83,8 @@ class CompactionPicker {
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore, const std::string& trim_ts);
|
||||
uint64_t max_file_num_to_ignore, const std::string& trim_ts,
|
||||
const std::string& full_history_ts_low);
|
||||
|
||||
// The maximum allowed output level. Default value is NumberLevels() - 1.
|
||||
virtual int MaxOutputLevel() const { return NumberLevels() - 1; }
|
||||
@@ -203,13 +205,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 +227,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);
|
||||
@@ -278,7 +286,8 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots*/,
|
||||
const SnapshotChecker* /*snapshot_checker*/,
|
||||
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */,
|
||||
bool /*require_max_output_level*/ = false) override {
|
||||
const std::string& /*full_history_ts_low*/,
|
||||
bool /*require_max_output_level*/) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -292,8 +301,8 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
const CompactRangeOptions& /*compact_range_options*/,
|
||||
const InternalKey* /*begin*/, const InternalKey* /*end*/,
|
||||
InternalKey** /*compaction_end*/, bool* /*manual_conflict*/,
|
||||
uint64_t /*max_file_num_to_ignore*/,
|
||||
const std::string& /*trim_ts*/) override {
|
||||
uint64_t /*max_file_num_to_ignore*/, const std::string& /*trim_ts*/,
|
||||
const std::string& /*full_history_ts_low*/) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -419,12 +419,19 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
return c;
|
||||
}
|
||||
|
||||
// The full_history_ts_low parameter is used to control bottommost file marking
|
||||
// for compaction when user-defined timestamps (UDT) are enabled.
|
||||
|
||||
// TODO leverage full_history_ts_low for FIFO compaction, by trigggerring
|
||||
// compaction early for data that has already expired to achieve the goal of TTL
|
||||
// enforced compliance.
|
||||
Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
|
||||
LogBuffer* log_buffer, const std::string& /* full_history_ts_low */,
|
||||
bool /* require_max_output_level*/) {
|
||||
Compaction* c = nullptr;
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
c = PickTTLCompaction(cf_name, mutable_cf_options, mutable_db_options,
|
||||
@@ -449,7 +456,8 @@ Compaction* FIFOCompactionPicker::PickCompactionForCompactRange(
|
||||
const CompactRangeOptions& /*compact_range_options*/,
|
||||
const InternalKey* /*begin*/, const InternalKey* /*end*/,
|
||||
InternalKey** compaction_end, bool* /*manual_conflict*/,
|
||||
uint64_t /*max_file_num_to_ignore*/, const std::string& /*trim_ts*/) {
|
||||
uint64_t /*max_file_num_to_ignore*/, const std::string& /*trim_ts*/,
|
||||
const std::string& full_history_ts_low) {
|
||||
#ifdef NDEBUG
|
||||
(void)input_level;
|
||||
(void)output_level;
|
||||
@@ -458,10 +466,10 @@ Compaction* FIFOCompactionPicker::PickCompactionForCompactRange(
|
||||
assert(output_level == 0);
|
||||
*compaction_end = nullptr;
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.logger);
|
||||
Compaction* c =
|
||||
PickCompaction(cf_name, mutable_cf_options, mutable_db_options,
|
||||
/*existing_snapshots*/ {}, /*snapshot_checker*/ nullptr,
|
||||
vstorage, &log_buffer);
|
||||
Compaction* c = PickCompaction(
|
||||
cf_name, mutable_cf_options, mutable_db_options,
|
||||
/*existing_snapshots*/ {}, /*snapshot_checker*/ nullptr, vstorage,
|
||||
&log_buffer, full_history_ts_low, /* require_max_output_level */ false);
|
||||
log_buffer.FlushBufferToLog();
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* version, LogBuffer* log_buffer,
|
||||
const std::string& /* full_history_ts_low */,
|
||||
bool /* require_max_output_level*/ = false) override;
|
||||
|
||||
Compaction* PickCompactionForCompactRange(
|
||||
@@ -33,7 +34,8 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore, const std::string& trim_ts) override;
|
||||
uint64_t max_file_num_to_ignore, const std::string& trim_ts,
|
||||
const std::string& full_history_ts_low) override;
|
||||
|
||||
// The maximum allowed output level. Always returns 0.
|
||||
int MaxOutputLevel() const override { return 0; }
|
||||
|
||||
@@ -61,14 +61,16 @@ class LevelCompactionBuilder {
|
||||
LogBuffer* log_buffer,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableDBOptions& mutable_db_options)
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::string& full_history_ts_low)
|
||||
: cf_name_(cf_name),
|
||||
vstorage_(vstorage),
|
||||
compaction_picker_(compaction_picker),
|
||||
log_buffer_(log_buffer),
|
||||
mutable_cf_options_(mutable_cf_options),
|
||||
ioptions_(ioptions),
|
||||
mutable_db_options_(mutable_db_options) {}
|
||||
mutable_db_options_(mutable_db_options),
|
||||
full_history_ts_low_(full_history_ts_low) {}
|
||||
|
||||
// Pick and return a compaction.
|
||||
Compaction* PickCompaction();
|
||||
@@ -155,6 +157,7 @@ class LevelCompactionBuilder {
|
||||
const MutableCFOptions& mutable_cf_options_;
|
||||
const ImmutableOptions& ioptions_;
|
||||
const MutableDBOptions& mutable_db_options_;
|
||||
const std::string& full_history_ts_low_;
|
||||
// Pick a path ID to place a newly generated file, with its level
|
||||
static uint32_t GetPathId(const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
@@ -571,7 +574,8 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
// being compacted). Since we just changed compaction score, we recalculate it
|
||||
// here
|
||||
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
|
||||
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_,
|
||||
full_history_ts_low_);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -976,10 +980,11 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots */,
|
||||
const SnapshotChecker* /*snapshot_checker*/, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
|
||||
LogBuffer* log_buffer, const std::string& full_history_ts_low,
|
||||
bool /* require_max_output_level*/) {
|
||||
LevelCompactionBuilder builder(cf_name, vstorage, this, log_buffer,
|
||||
mutable_cf_options, ioptions_,
|
||||
mutable_db_options);
|
||||
mutable_db_options, full_history_ts_low);
|
||||
return builder.PickCompaction();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -26,6 +26,7 @@ class LevelCompactionPicker : public CompactionPicker {
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer,
|
||||
const std::string& full_history_ts_low,
|
||||
bool /*require_max_output_level*/ = false) override;
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -39,7 +39,7 @@ class UniversalCompactionBuilder {
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
UniversalCompactionPicker* picker, LogBuffer* log_buffer,
|
||||
bool require_max_output_level)
|
||||
bool require_max_output_level, const std::string& full_history_ts_low)
|
||||
: ioptions_(ioptions),
|
||||
icmp_(icmp),
|
||||
cf_name_(cf_name),
|
||||
@@ -50,7 +50,8 @@ class UniversalCompactionBuilder {
|
||||
log_buffer_(log_buffer),
|
||||
require_max_output_level_(require_max_output_level),
|
||||
allow_ingest_behind_(ioptions.cf_allow_ingest_behind ||
|
||||
ioptions.allow_ingest_behind) {
|
||||
ioptions.allow_ingest_behind),
|
||||
full_history_ts_low_(full_history_ts_low) {
|
||||
assert(icmp_);
|
||||
const auto* ucmp = icmp_->user_comparator();
|
||||
assert(ucmp);
|
||||
@@ -450,6 +451,7 @@ class UniversalCompactionBuilder {
|
||||
std::map<uint64_t, size_t> file_marked_for_compaction_to_sorted_run_index_;
|
||||
bool require_max_output_level_;
|
||||
bool allow_ingest_behind_;
|
||||
const std::string& full_history_ts_low_;
|
||||
|
||||
std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, int last_level,
|
||||
@@ -595,16 +597,20 @@ bool UniversalCompactionPicker::NeedsCompaction(
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO leverage full_history_ts_low in universal compaction picking. It could
|
||||
// help reduce the same infinite compaction loop issue found in level
|
||||
// compaction.
|
||||
Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer, bool require_max_output_level) {
|
||||
LogBuffer* log_buffer, const std::string& full_history_ts_low,
|
||||
bool require_max_output_level) {
|
||||
UniversalCompactionBuilder builder(
|
||||
ioptions_, icmp_, cf_name, mutable_cf_options, mutable_db_options,
|
||||
existing_snapshots, snapshot_checker, vstorage, this, log_buffer,
|
||||
require_max_output_level);
|
||||
require_max_output_level, full_history_ts_low);
|
||||
return builder.PickCompaction();
|
||||
}
|
||||
|
||||
@@ -825,7 +831,8 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
RecordInHistogram(ioptions_.stats, NUM_FILES_IN_SINGLE_COMPACTION, num_files);
|
||||
|
||||
picker_->RegisterCompaction(c);
|
||||
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_);
|
||||
vstorage_->ComputeCompactionScore(ioptions_, mutable_cf_options_,
|
||||
full_history_ts_low_);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("UniversalCompactionBuilder::PickCompaction:Return",
|
||||
c);
|
||||
@@ -1544,9 +1551,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 +1573,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);
|
||||
|
||||
@@ -26,7 +26,8 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer, bool require_max_output_level = false) override;
|
||||
LogBuffer* log_buffer, const std::string& full_history_ts_low,
|
||||
bool require_max_output_level = false) override;
|
||||
int MaxOutputLevel() const override { return NumberLevels() - 1; }
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
@@ -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.
|
||||
|
||||
+4
-4
@@ -65,7 +65,7 @@ Status VerifySstFileChecksum(const Options& options,
|
||||
}
|
||||
|
||||
Status VerifySstFileChecksumInternal(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const FileOptions& file_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::string& file_path,
|
||||
const SequenceNumber& largest_seqno) {
|
||||
@@ -74,8 +74,8 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
InternalKeyComparator internal_comparator(options.comparator);
|
||||
ImmutableOptions ioptions(options);
|
||||
|
||||
Status s = ioptions.fs->NewRandomAccessFile(
|
||||
file_path, FileOptions(env_options), &file, nullptr);
|
||||
Status s =
|
||||
ioptions.fs->NewRandomAccessFile(file_path, file_options, &file, nullptr);
|
||||
if (s.ok()) {
|
||||
s = ioptions.fs->GetFileSize(file_path, IOOptions(), &file_size, nullptr);
|
||||
} else {
|
||||
@@ -94,7 +94,7 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
const bool kImmortal = true;
|
||||
auto reader_options = TableReaderOptions(
|
||||
ioptions, options.prefix_extractor, options.compression_manager.get(),
|
||||
env_options, internal_comparator, options.block_protection_bytes_per_key,
|
||||
file_options, internal_comparator, options.block_protection_bytes_per_key,
|
||||
false /* skip_filters */, !kImmortal, false /* force_direct_prefetch */,
|
||||
-1 /* level */);
|
||||
reader_options.largest_seqno = largest_seqno;
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
|
||||
#pragma once
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
Status VerifySstFileChecksumInternal(const Options& options,
|
||||
const EnvOptions& env_options,
|
||||
const FileOptions& file_options,
|
||||
const ReadOptions& read_options,
|
||||
const std::string& file_path,
|
||||
const SequenceNumber& largest_seqno = 0);
|
||||
|
||||
+194
-24
@@ -242,6 +242,43 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
|
||||
Status::Code::kNotSupported);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ReadOnlyDBFlushWAL) {
|
||||
// Test that FlushWAL returns NotSupported on read-only DB, and that
|
||||
// GetLiveFilesStorageInfo works correctly even with manual_wal_flush=true.
|
||||
// This is a regression test for a bug where GetLiveFilesStorageInfo would
|
||||
// crash on read-only DBs with manual_wal_flush=true because FlushWAL
|
||||
// accessed logs_.back() on an empty deque.
|
||||
auto options = CurrentOptions();
|
||||
options.manual_wal_flush = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_OK(Put("bar", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("baz", "v3")); // Unflushed data in WAL
|
||||
Close();
|
||||
|
||||
// Reopen as read-only
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
ASSERT_EQ("v2", Get("bar"));
|
||||
ASSERT_EQ("v3", Get("baz"));
|
||||
|
||||
// FlushWAL should return NotSupported (not crash)
|
||||
ASSERT_EQ(db_->FlushWAL(/*sync=*/false).code(), Status::Code::kNotSupported);
|
||||
ASSERT_EQ(db_->FlushWAL(/*sync=*/true).code(), Status::Code::kNotSupported);
|
||||
|
||||
// GetLiveFilesStorageInfo should succeed (previously crashed with
|
||||
// manual_wal_flush=true because it called FlushWAL which accessed
|
||||
// logs_.back() on empty deque)
|
||||
LiveFilesStorageInfoOptions lfsi_opts;
|
||||
lfsi_opts.wal_size_for_flush = 0;
|
||||
std::vector<LiveFileStorageInfo> files;
|
||||
ASSERT_OK(db_->GetLiveFilesStorageInfo(lfsi_opts, &files));
|
||||
ASSERT_GT(files.size(), 0);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ReadOnlyDBWithWriteDBIdToManifestSet) {
|
||||
auto options = CurrentOptions();
|
||||
options.write_dbid_to_manifest = false;
|
||||
@@ -675,30 +712,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 {
|
||||
@@ -3892,6 +3905,75 @@ TEST_F(DBBasicTest, SkipWALIfMissingTableFiles) {
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, BestEffortRecoveryFailureWithTableCacheUseAfterFree) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
// Force multiple manifest files
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Disable file deletions to preserve old manifest files for
|
||||
// best-efforts recovery to succeed
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
|
||||
// Create multiple SST files to populate TableCache during
|
||||
// best-efforts recovery
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put("key" + std::to_string(i),
|
||||
std::string(1000, static_cast<char>('a' + i))));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Verify we have multiple manifest files
|
||||
std::vector<std::string> files;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &files));
|
||||
int manifest_count = 0;
|
||||
for (const auto& file : files) {
|
||||
if (file.find("MANIFEST") != std::string::npos) {
|
||||
manifest_count++;
|
||||
}
|
||||
}
|
||||
ASSERT_GE(manifest_count, 2);
|
||||
|
||||
// Inject corruption after TableCache is populated (count > 3), but only once
|
||||
// (injected flag) to allow best-effort recovery to trigger retry and succeed.
|
||||
// This coerce the bug: first recovery caches SSTs with reference to column
|
||||
// family's options in table cache and retry deletes column family so the
|
||||
// reference becomes dangling.
|
||||
int count = 0;
|
||||
bool injected = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionBuilder::CheckConsistencyBeforeReturn", [&](void* arg) {
|
||||
count++;
|
||||
if (count > 3 && !injected) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
*(static_cast<Status*>(arg)) =
|
||||
Status::Corruption("Injected corruption");
|
||||
injected = true;
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
options.best_efforts_recovery = true;
|
||||
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_OK(s);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
std::string value;
|
||||
// Without the fix, ASAN detects use-after-free when accessing cached SST
|
||||
// files that hold dangling references to deleted ioptions.
|
||||
s = db_->Get(ReadOptions(), "key" + std::to_string(i), &value);
|
||||
ASSERT_TRUE(s.ok() || s.IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DisableTrackWal) {
|
||||
// If WAL tracking was enabled, and then disabled during reopen,
|
||||
// the previously tracked WALs should be removed from MANIFEST.
|
||||
@@ -5392,6 +5474,94 @@ INSTANTIATE_TEST_CASE_P(DBBasicTestDeadline, DBBasicTestDeadline,
|
||||
::testing::Values(std::make_tuple(true, false),
|
||||
std::make_tuple(false, true),
|
||||
std::make_tuple(true, true)));
|
||||
|
||||
// FileSystemWrapper that captures FileOptions passed to NewRandomAccessFile
|
||||
// for .sst files, so we can verify file_checksum fields are populated.
|
||||
class ChecksumCapturingFS : public FileSystemWrapper {
|
||||
public:
|
||||
explicit ChecksumCapturingFS(const std::shared_ptr<FileSystem>& base)
|
||||
: FileSystemWrapper(base) {}
|
||||
|
||||
static const char* kClassName() { return "ChecksumCapturingFS"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
const FileOptions& opts,
|
||||
std::unique_ptr<FSRandomAccessFile>* result,
|
||||
IODebugContext* dbg) override {
|
||||
if (fname.find(".sst") != std::string::npos) {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
captured_file_checksum_ = opts.file_checksum;
|
||||
captured_file_checksum_func_name_ = opts.file_checksum_func_name;
|
||||
capture_count_++;
|
||||
}
|
||||
return target()->NewRandomAccessFile(fname, opts, result, dbg);
|
||||
}
|
||||
|
||||
std::string GetCapturedFileChecksum() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return captured_file_checksum_;
|
||||
}
|
||||
|
||||
std::string GetCapturedFileChecksumFuncName() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return captured_file_checksum_func_name_;
|
||||
}
|
||||
|
||||
int GetCaptureCount() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
return capture_count_;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
std::lock_guard<std::mutex> lock(mu_);
|
||||
captured_file_checksum_.clear();
|
||||
captured_file_checksum_func_name_.clear();
|
||||
capture_count_ = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex mu_;
|
||||
std::string captured_file_checksum_;
|
||||
std::string captured_file_checksum_func_name_;
|
||||
int capture_count_ = 0;
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTest, FileChecksumInFileOptions) {
|
||||
// Verify that file_checksum and file_checksum_func_name from FileMetaData
|
||||
// are propagated through FileOptions when opening SST files.
|
||||
auto capturing_fs =
|
||||
std::make_shared<ChecksumCapturingFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, capturing_fs));
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env.get();
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write data and flush to create an SST with a file checksum.
|
||||
ASSERT_OK(Put("key1", "value1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Reset captures, then reopen to trigger TableCache SST open.
|
||||
capturing_fs->Reset();
|
||||
Reopen(options);
|
||||
|
||||
// Read to trigger SST open through TableCache::GetTableReader.
|
||||
ASSERT_EQ("value1", Get("key1"));
|
||||
|
||||
// Verify that checksum fields were populated.
|
||||
ASSERT_GT(capturing_fs->GetCaptureCount(), 0);
|
||||
ASSERT_FALSE(capturing_fs->GetCapturedFileChecksum().empty());
|
||||
ASSERT_NE(capturing_fs->GetCapturedFileChecksumFuncName(),
|
||||
capturing_fs->GetCapturedFileChecksum());
|
||||
ASSERT_EQ(capturing_fs->GetCapturedFileChecksumFuncName(),
|
||||
"FileChecksumCrc32c");
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -101,7 +101,7 @@ TEST_F(DBFlushTest, SyncFail) {
|
||||
TEST_SYNC_POINT("DBFlushTest::SyncFail:2");
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
// Now the background job will do the flush; wait for it.
|
||||
// Returns the IO error happend during flush.
|
||||
// Returns the IO error happened during flush.
|
||||
ASSERT_NOK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ("", FilesPerLevel()); // flush failed.
|
||||
Destroy(options);
|
||||
@@ -518,11 +518,11 @@ TEST_F(DBFlushTest, StatisticsGarbageInsertAndDeletes) {
|
||||
// Note : one set of delete for KEY1, KEY2, KEY3 is written to
|
||||
// SSTable to propagate the delete operations to K-V pairs
|
||||
// that could have been inserted into the database during past Flush
|
||||
// opeartions.
|
||||
// operations.
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH -=
|
||||
KEY1.size() + KEY2.size() + KEY3.size() + 3 * sizeof(uint64_t);
|
||||
|
||||
// Additional useful paylaod.
|
||||
// Additional useful payload.
|
||||
ASSERT_OK(Delete(KEY4));
|
||||
ASSERT_OK(Delete(KEY5));
|
||||
ASSERT_OK(Delete(KEY6));
|
||||
@@ -614,7 +614,7 @@ TEST_F(DBFlushTest, StatisticsGarbageRangeDeletes) {
|
||||
|
||||
// Note : one set of deleteRange for (KEY1, KEY2) and (KEY2, KEY3) is written
|
||||
// to SSTable to propagate the deleteRange operations to K-V pairs that could
|
||||
// have been inserted into the database during past Flush opeartions.
|
||||
// have been inserted into the database during past Flush operations.
|
||||
EXPECTED_MEMTABLE_GARBAGE_BYTES_AT_FLUSH -=
|
||||
(KEY1.size() + KEY2.size() + sizeof(uint64_t)) +
|
||||
(KEY2.size() + KEY3.size() + sizeof(uint64_t));
|
||||
@@ -842,7 +842,7 @@ TEST_F(DBFlushTest, FixFlushReasonRaceFromConcurrentFlushes) {
|
||||
ASSERT_OK(Put(1, Key(idx), std::string(1, 'v')));
|
||||
}
|
||||
|
||||
// To coerce a manual flush happenning in the middle of GetLiveFiles's flush,
|
||||
// To coerce a manual flush happening in the middle of GetLiveFiles's flush,
|
||||
// we need to pause background flush thread and enable it later.
|
||||
std::shared_ptr<test::SleepingBackgroundTask> sleeping_task =
|
||||
std::make_shared<test::SleepingBackgroundTask>();
|
||||
@@ -851,7 +851,7 @@ TEST_F(DBFlushTest, FixFlushReasonRaceFromConcurrentFlushes) {
|
||||
sleeping_task.get(), Env::Priority::HIGH);
|
||||
sleeping_task->WaitUntilSleeping();
|
||||
|
||||
// Coerce a manual flush happenning in the middle of GetLiveFiles's flush
|
||||
// Coerce a manual flush happening in the middle of GetLiveFiles's flush
|
||||
bool get_live_files_paused_at_sync_point = false;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::AtomicFlushMemTables:AfterScheduleFlush", [&](void* /* arg */) {
|
||||
@@ -1428,7 +1428,7 @@ TEST_F(DBFlushTest, MemPurgeDeleteAndDeleteRange) {
|
||||
Close();
|
||||
}
|
||||
|
||||
// Create a Compaction Fitler that will be invoked
|
||||
// Create a Compaction Filter that will be invoked
|
||||
// at flush time and will update the value of a KV pair
|
||||
// if the key string is "lower" than the filter_key_ string.
|
||||
class ConditionalUpdateFilter : public CompactionFilter {
|
||||
|
||||
+130
-57
@@ -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()));
|
||||
|
||||
@@ -1177,23 +1177,38 @@ FSDirectory* DBImpl::GetDataDir(ColumnFamilyData* cfd, size_t path_id) const {
|
||||
}
|
||||
|
||||
Status DBImpl::SetOptions(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::unordered_map<std::string, std::string>& options_map) {
|
||||
const std::unordered_map<ColumnFamilyHandle*,
|
||||
std::unordered_map<std::string, std::string>>&
|
||||
column_families_opts_map) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
if (options_map.empty()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"SetOptions() on column family [%s], empty input",
|
||||
cfd->GetName().c_str());
|
||||
return Status::InvalidArgument("empty input");
|
||||
if (column_families_opts_map.empty()) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
for (const auto& cf_opts : column_families_opts_map) {
|
||||
if (cf_opts.second.empty()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"SetOptions() on column family [%s], empty input",
|
||||
cf_opts.first->GetName().c_str());
|
||||
return Status::InvalidArgument("empty input");
|
||||
}
|
||||
}
|
||||
|
||||
autovector<std::pair<ColumnFamilyData*,
|
||||
const std::unordered_map<std::string, std::string>*>>
|
||||
column_family_datas;
|
||||
for (const auto& cf_opts : column_families_opts_map) {
|
||||
column_family_datas.push_back(
|
||||
{static_cast_with_check<ColumnFamilyHandleImpl>(cf_opts.first)->cfd(),
|
||||
&cf_opts.second});
|
||||
}
|
||||
|
||||
InstrumentedMutexLock ol(&options_mutex_);
|
||||
MutableCFOptions new_options_copy; // For logging outside of DB mutex
|
||||
autovector<MutableCFOptions>
|
||||
new_options_copy; // For logging outside of DB mutex
|
||||
Status s;
|
||||
Status persist_options_status;
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
@@ -1216,68 +1231,104 @@ Status DBImpl::SetOptions(
|
||||
// Thus aren't releasing the DB mutex from LogAndApply calling pre_cb,
|
||||
// through installing the new Version until the end of this block, after
|
||||
// installing the new SuperVersion.
|
||||
auto pre_cb = [&]() -> Status {
|
||||
Status cb_s = cfd->SetOptions(db_options, options_map);
|
||||
if (cb_s.ok()) {
|
||||
new_options_copy = cfd->GetLatestMutableCFOptions();
|
||||
}
|
||||
return cb_s;
|
||||
};
|
||||
VersionEdit dummy_edit;
|
||||
dummy_edit.MarkNoManifestWriteDummy();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::SetOptions:dummy_edit", &dummy_edit);
|
||||
s = versions_->LogAndApply(
|
||||
cfd, read_options, write_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir(), false /*new_descriptor_log=*/,
|
||||
nullptr /*new_opts*/, {} /*manifest_wcb*/, pre_cb);
|
||||
if (!versions_->io_status().ok()) {
|
||||
assert(!s.ok());
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
for (const auto& cfd_opts : column_family_datas) {
|
||||
auto* cfd = cfd_opts.first;
|
||||
const auto* options_map_ptr = cfd_opts.second;
|
||||
auto pre_cb = [&]() -> Status {
|
||||
Status cb_s = cfd->SetOptions(db_options, *options_map_ptr);
|
||||
if (cb_s.ok()) {
|
||||
new_options_copy.emplace_back(cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
return cb_s;
|
||||
};
|
||||
|
||||
s = versions_->LogAndApply(
|
||||
cfd, read_options, write_options, &dummy_edit, &mutex_,
|
||||
directories_.GetDbDir(), false /*new_descriptor_log=*/,
|
||||
nullptr /*new_opts*/, {} /*manifest_wcb*/, pre_cb);
|
||||
if (!versions_->io_status().ok()) {
|
||||
assert(!s.ok());
|
||||
error_handler_.SetBGError(versions_->io_status(),
|
||||
BackgroundErrorReason::kManifestWrite);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
// Trigger possible flush/compactions. This has to be before we persist
|
||||
// options to file, otherwise there will be a deadlock with writer
|
||||
// thread.
|
||||
InstallSuperVersionForConfigChange(cfd, &sv_context);
|
||||
for (const auto& cfd_opts : column_family_datas) {
|
||||
InstallSuperVersionForConfigChange(cfd_opts.first, &sv_context);
|
||||
}
|
||||
persist_options_status =
|
||||
WriteOptionsFile(write_options, true /*db_mutex_already_held*/);
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
assert(new_options_copy == cfd->GetLatestMutableCFOptions());
|
||||
assert(cfd->GetLatestMutableCFOptions() ==
|
||||
cfd->GetCurrentMutableCFOptions());
|
||||
assert(cfd->GetCurrentMutableCFOptions() ==
|
||||
cfd->current()->GetMutableCFOptions());
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < column_family_datas.size(); ++i) {
|
||||
auto* cfd = column_family_datas[i].first;
|
||||
assert(new_options_copy[i] == cfd->GetLatestMutableCFOptions());
|
||||
assert(cfd->GetLatestMutableCFOptions() ==
|
||||
cfd->GetCurrentMutableCFOptions());
|
||||
assert(cfd->GetCurrentMutableCFOptions() ==
|
||||
cfd->current()->GetMutableCFOptions());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
sv_context.Clean();
|
||||
|
||||
if (s.ok() && (options_map.count("preserve_internal_time_seconds") > 0 ||
|
||||
options_map.count("preclude_last_level_data_seconds") > 0)) {
|
||||
s = RegisterRecordSeqnoTimeWorker();
|
||||
if (s.ok()) {
|
||||
bool needs_seqno_worker = false;
|
||||
for (const auto& cf_opts : column_families_opts_map) {
|
||||
if (cf_opts.second.count("preserve_internal_time_seconds") > 0 ||
|
||||
cf_opts.second.count("preclude_last_level_data_seconds") > 0) {
|
||||
needs_seqno_worker = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (needs_seqno_worker) {
|
||||
s = RegisterRecordSeqnoTimeWorker();
|
||||
}
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"SetOptions() on column family [%s], inputs:", cfd->GetName().c_str());
|
||||
for (const auto& o : options_map) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s: %s\n", o.first.c_str(),
|
||||
o.second.c_str());
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"SetOptions() on [%zu] column families, inputs:",
|
||||
column_family_datas.size());
|
||||
for (size_t i = 0; i < column_family_datas.size(); ++i) {
|
||||
const auto* cfd = column_family_datas[i].first;
|
||||
const auto* options_map_ptr = column_family_datas[i].second;
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Set options on column family [%s] (%zu/%zu), inputs:",
|
||||
cfd->GetName().c_str(), i, column_family_datas.size());
|
||||
for (const auto& o : *options_map_ptr) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "%s: %s\n",
|
||||
o.first.c_str(), o.second.c_str());
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] SetOptions() succeeded", cfd->GetName().c_str());
|
||||
new_options_copy.Dump(immutable_db_options_.info_log.get());
|
||||
for (size_t i = 0; i < column_family_datas.size(); ++i) {
|
||||
const auto* cfd = column_family_datas[i].first;
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Set options on column family [%s] (%zu/%zu) succeeded, "
|
||||
"updated CF options:",
|
||||
cfd->GetName().c_str(), i, column_family_datas.size());
|
||||
new_options_copy[i].Dump(immutable_db_options_.info_log.get());
|
||||
}
|
||||
if (!persist_options_status.ok()) {
|
||||
// NOTE: WriteOptionsFile already logs on failure
|
||||
s = persist_options_status;
|
||||
}
|
||||
} else {
|
||||
persist_options_status.PermitUncheckedError(); // less important
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log, "[%s] SetOptions() failed",
|
||||
cfd->GetName().c_str());
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log, "SetOptions() failed: %s",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return s;
|
||||
@@ -1412,7 +1463,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_);
|
||||
@@ -4351,7 +4402,8 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->AllowIngestBehind()) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(
|
||||
oldest_snapshot, /*allow_ingest_behind=*/false);
|
||||
oldest_snapshot, /*allow_ingest_behind=*/false,
|
||||
cfd->ioptions().user_comparator, cfd->GetFullHistoryTsLow());
|
||||
if (!cfd->current()
|
||||
->storage_info()
|
||||
->BottommostFilesMarkedForCompaction()
|
||||
@@ -4987,7 +5039,8 @@ Status DBImpl::DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
if (!deleted_files.empty()) {
|
||||
vstorage->ComputeCompactionScore(cfd->ioptions(),
|
||||
cfd->GetLatestMutableCFOptions());
|
||||
cfd->GetLatestMutableCFOptions(),
|
||||
cfd->GetFullHistoryTsLow());
|
||||
}
|
||||
if (edit.GetDeletedFiles().empty()) {
|
||||
job_context.Clean();
|
||||
@@ -5047,6 +5100,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 +5572,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 +5599,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
|
||||
@@ -6405,8 +6471,11 @@ Status DBImpl::VerifyChecksumInternal(const ReadOptions& read_options,
|
||||
fmeta->file_checksum_func_name, fname,
|
||||
read_options);
|
||||
} else {
|
||||
FileOptions fopts = file_options_;
|
||||
fopts.file_checksum = fmeta->file_checksum;
|
||||
fopts.file_checksum_func_name = fmeta->file_checksum_func_name;
|
||||
s = ROCKSDB_NAMESPACE::VerifySstFileChecksumInternal(
|
||||
opts, file_options_, read_options, fname, fd.largest_seqno);
|
||||
opts, fopts, read_options, fname, fd.largest_seqno);
|
||||
}
|
||||
RecordTick(stats_, VERIFY_CHECKSUM_READ_BYTES,
|
||||
IOSTATS(bytes_read) - prev_bytes_read);
|
||||
@@ -6474,12 +6543,15 @@ Status DBImpl::VerifyFullFileChecksum(const std::string& file_checksum_expected,
|
||||
}
|
||||
std::string file_checksum;
|
||||
std::string func_name;
|
||||
FileOptions fopts;
|
||||
fopts.file_checksum = file_checksum_expected;
|
||||
fopts.file_checksum_func_name = func_name_expected;
|
||||
s = ROCKSDB_NAMESPACE::GenerateOneFileChecksum(
|
||||
fs_.get(), fname, immutable_db_options_.file_checksum_gen_factory.get(),
|
||||
func_name_expected, &file_checksum, &func_name,
|
||||
read_options.readahead_size, immutable_db_options_.allow_mmap_reads,
|
||||
io_tracer_, immutable_db_options_.rate_limiter.get(), read_options,
|
||||
immutable_db_options_.stats, immutable_db_options_.clock);
|
||||
immutable_db_options_.stats, immutable_db_options_.clock, fopts);
|
||||
if (s.ok()) {
|
||||
assert(func_name_expected == func_name);
|
||||
if (file_checksum != file_checksum_expected) {
|
||||
@@ -6838,7 +6910,8 @@ void DBImpl::TriggerPeriodicCompaction() {
|
||||
if (cfd->GetLatestCFOptions().periodic_compaction_seconds &&
|
||||
!cfd->queued_for_compaction()) {
|
||||
cfd->current()->storage_info()->ComputeCompactionScore(
|
||||
cfd->ioptions(), cfd->GetLatestMutableCFOptions());
|
||||
cfd->ioptions(), cfd->GetLatestMutableCFOptions(),
|
||||
cfd->GetFullHistoryTsLow());
|
||||
EnqueuePendingCompaction(cfd);
|
||||
if (cfd->queued_for_compaction()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
|
||||
+16
-2
@@ -458,8 +458,9 @@ class DBImpl : public DB {
|
||||
|
||||
using DB::SetOptions;
|
||||
Status SetOptions(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::unordered_map<std::string, std::string>& options_map) override;
|
||||
const std::unordered_map<ColumnFamilyHandle*,
|
||||
std::unordered_map<std::string, std::string>>&
|
||||
column_families_opts_map) override;
|
||||
|
||||
Status SetDBOptions(
|
||||
const std::unordered_map<std::string, std::string>& options_map) override;
|
||||
@@ -573,6 +574,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 +2402,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,57 @@ 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,
|
||||
f->min_timestamp, f->max_timestamp);
|
||||
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 +1562,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(
|
||||
@@ -1538,8 +1646,8 @@ Status DBImpl::CompactFilesImpl(
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
// being compacted). Since we just changed compaction score, we recalculate it
|
||||
// here.
|
||||
version->storage_info()->ComputeCompactionScore(cfd->ioptions(),
|
||||
c->mutable_cf_options());
|
||||
version->storage_info()->ComputeCompactionScore(
|
||||
cfd->ioptions(), c->mutable_cf_options(), cfd->GetFullHistoryTsLow());
|
||||
|
||||
compaction_job.Prepare(std::nullopt /*subcompact to be computed*/);
|
||||
|
||||
@@ -1864,7 +1972,8 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
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);
|
||||
f->user_defined_timestamps_persisted, f->min_timestamp,
|
||||
f->max_timestamp);
|
||||
}
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[%s] Apply version edit:\n%s", cfd->GetName().c_str(),
|
||||
@@ -2782,16 +2891,8 @@ Status DBImpl::WaitForFlushMemTables(
|
||||
|
||||
Status DBImpl::EnableAutoCompaction(
|
||||
const std::vector<ColumnFamilyHandle*>& column_family_handles) {
|
||||
Status s;
|
||||
for (auto cf_ptr : column_family_handles) {
|
||||
Status status =
|
||||
this->SetOptions(cf_ptr, {{"disable_auto_compactions", "false"}});
|
||||
if (!status.ok()) {
|
||||
s = status;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
return SetOptions(column_family_handles,
|
||||
{{"disable_auto_compactions", "false"}});
|
||||
}
|
||||
|
||||
// NOTE: Calling DisableManualCompaction() may overwrite the
|
||||
@@ -3732,7 +3833,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
->current()
|
||||
->storage_info()
|
||||
->ComputeCompactionScore(c->immutable_options(),
|
||||
c->mutable_cf_options());
|
||||
c->mutable_cf_options(),
|
||||
cfd->GetFullHistoryTsLow());
|
||||
EnqueuePendingCompaction(cfd);
|
||||
|
||||
c.reset();
|
||||
@@ -3989,7 +4091,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
in_file->unique_id,
|
||||
in_file->compensated_range_deletion_size,
|
||||
in_file->tail_size,
|
||||
in_file->user_defined_timestamps_persisted};
|
||||
in_file->user_defined_timestamps_persisted,
|
||||
in_file->min_timestamp,
|
||||
in_file->max_timestamp};
|
||||
|
||||
out_files.push_back(std::move(out_file_metadata));
|
||||
}
|
||||
@@ -4074,35 +4178,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 +4188,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 +4208,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));
|
||||
@@ -4326,7 +4398,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
->current()
|
||||
->storage_info()
|
||||
->ComputeCompactionScore(c->immutable_options(),
|
||||
c->mutable_cf_options());
|
||||
c->mutable_cf_options(),
|
||||
cfd->GetFullHistoryTsLow());
|
||||
EnqueuePendingCompaction(cfd);
|
||||
}
|
||||
}
|
||||
@@ -4432,7 +4505,7 @@ Compaction* DBImpl::CreateIntendedCompactionForwardedToBottomPriorityPool(
|
||||
nullptr /* snapshot_checker */, c->compaction_reason());
|
||||
|
||||
cfd->compaction_picker()->RegisterCompaction(intended_compaction);
|
||||
vstorage->ComputeCompactionScore(io, mo);
|
||||
vstorage->ComputeCompactionScore(io, mo, cfd->GetFullHistoryTsLow());
|
||||
intended_compaction->FinalizeInputInfo(cfd->current());
|
||||
|
||||
return intended_compaction;
|
||||
@@ -4804,7 +4877,8 @@ void DBImpl::ResetBottomPriCompactionIntent(ColumnFamilyData* cfd,
|
||||
std::unique_ptr<Compaction>& c) {
|
||||
c->ReleaseCompactionFiles(Status::OK());
|
||||
cfd->current()->storage_info()->ComputeCompactionScore(
|
||||
c->immutable_options(), c->mutable_cf_options());
|
||||
c->immutable_options(), c->mutable_cf_options(),
|
||||
cfd->GetFullHistoryTsLow());
|
||||
c.reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
// Since we have some more files to compact, we should also recompute
|
||||
// compaction score
|
||||
vstorage->ComputeCompactionScore(cfd->ioptions(),
|
||||
cfd->GetLatestMutableCFOptions());
|
||||
cfd->GetLatestMutableCFOptions(),
|
||||
cfd->GetFullHistoryTsLow());
|
||||
EnqueuePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
@@ -143,7 +144,8 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
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);
|
||||
f->user_defined_timestamps_persisted, f->min_timestamp,
|
||||
f->max_timestamp);
|
||||
}
|
||||
|
||||
status = versions_->LogAndApply(cfd, read_options, write_options, &edit,
|
||||
|
||||
@@ -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 */,
|
||||
@@ -657,7 +657,8 @@ Status DBImpl::Recover(
|
||||
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);
|
||||
f->tail_size, f->user_defined_timestamps_persisted,
|
||||
f->min_timestamp, f->max_timestamp);
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] Moving #%" PRIu64
|
||||
" from from_level-%d to from_level-%d %" PRIu64
|
||||
|
||||
@@ -121,6 +121,11 @@ class DBImplReadOnly : public DBImpl {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::FlushWAL;
|
||||
Status FlushWAL(const FlushWALOptions& /*options*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DB::IngestExternalFile;
|
||||
Status IngestExternalFile(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
|
||||
@@ -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
|
||||
@@ -216,9 +216,9 @@ class DBImplSecondary : public DBImpl {
|
||||
|
||||
using DBImpl::SetOptions;
|
||||
Status SetOptions(
|
||||
ColumnFamilyHandle* /*cfd*/,
|
||||
const std::unordered_map<std::string, std::string>& /*options_map*/)
|
||||
override {
|
||||
const std::unordered_map<ColumnFamilyHandle*,
|
||||
std::unordered_map<std::string, std::string>>&
|
||||
/*column_families_opts_map*/) override {
|
||||
// Currently not supported because changing certain options may cause
|
||||
// flush/compaction and/or write to MANIFEST.
|
||||
return Status::NotSupported("Not supported operation in secondary mode.");
|
||||
|
||||
@@ -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,503 @@ 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();
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, StatisticsTest) {
|
||||
// Test that multi scan statistics are properly recorded
|
||||
auto options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
// Use small block size to ensure multiple blocks
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 256;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create data across multiple blocks
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(3) << std::setfill('0') << i;
|
||||
// Use larger values to ensure multiple blocks
|
||||
ASSERT_OK(Put("k" + ss.str(), std::string(100, 'v')));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Reset stats before multi scan
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
|
||||
// Set up two scan ranges
|
||||
std::vector<std::string> key_ranges({"k010", "k030", "k060", "k080"});
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
|
||||
// Iterate through all ranges
|
||||
int count = 0;
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
(void)it;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
}
|
||||
ASSERT_EQ(count, 40); // 20 keys per range
|
||||
iter.reset();
|
||||
|
||||
// Check statistics
|
||||
// MULTISCAN_PREPARE_CALLS should be at least 1
|
||||
ASSERT_GE(TestGetTickerCount(options, MULTISCAN_PREPARE_CALLS), 1);
|
||||
|
||||
// MULTISCAN_PREPARE_ERRORS should be 0
|
||||
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_PREPARE_ERRORS), 0);
|
||||
|
||||
// MULTISCAN_SEEK_ERRORS should be 0
|
||||
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_SEEK_ERRORS), 0);
|
||||
|
||||
// Blocks should be prefetched or from cache
|
||||
uint64_t blocks_prefetched =
|
||||
TestGetTickerCount(options, MULTISCAN_BLOCKS_PREFETCHED);
|
||||
uint64_t blocks_from_cache =
|
||||
TestGetTickerCount(options, MULTISCAN_BLOCKS_FROM_CACHE);
|
||||
ASSERT_GT(blocks_prefetched + blocks_from_cache, 0);
|
||||
|
||||
// If blocks were prefetched, prefetch bytes and IO requests should be > 0
|
||||
if (blocks_prefetched > 0) {
|
||||
ASSERT_GT(TestGetTickerCount(options, MULTISCAN_PREFETCH_BYTES), 0);
|
||||
uint64_t io_requests = TestGetTickerCount(options, MULTISCAN_IO_REQUESTS);
|
||||
ASSERT_GT(io_requests, 0);
|
||||
ASSERT_LE(io_requests, blocks_prefetched);
|
||||
}
|
||||
|
||||
// Wasted blocks should be 0 since we iterated through everything
|
||||
ASSERT_EQ(TestGetTickerCount(options, MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, StatisticsWastedBlocksTest) {
|
||||
// Test that wasted blocks are tracked when iteration is abandoned early
|
||||
auto options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
// Use small block size to ensure multiple blocks
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 256;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create data across multiple blocks
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(3) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put("k" + ss.str(), std::string(100, 'v')));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Reset stats before multi scan
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
|
||||
// Set up a large scan range
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert("k000", "k099");
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
|
||||
// Only iterate through a few keys, then abandon
|
||||
int count = 0;
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
(void)it;
|
||||
count++;
|
||||
if (count >= 5) {
|
||||
break; // Abandon iteration early
|
||||
}
|
||||
}
|
||||
if (count >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
}
|
||||
ASSERT_EQ(count, 5);
|
||||
|
||||
// Destroy iterator to trigger wasted blocks counting
|
||||
iter.reset();
|
||||
|
||||
uint64_t blocks_prefetched =
|
||||
TestGetTickerCount(options, MULTISCAN_BLOCKS_PREFETCHED);
|
||||
|
||||
// If blocks were prefetched, some should be wasted since we abandoned early
|
||||
if (blocks_prefetched > 1) {
|
||||
// We only read a few keys, so there should be wasted blocks
|
||||
ASSERT_GT(TestGetTickerCount(options, MULTISCAN_PREFETCH_BLOCKS_WASTED), 0);
|
||||
}
|
||||
}
|
||||
} // 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);
|
||||
@@ -1654,6 +1689,46 @@ TEST_F(DBOptionsTest, SetOptionsNoManifestWrite) {
|
||||
ASSERT_EQ(Get("x"), "x");
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetOptionsMultipleColumnFamilies) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = CurrentOptions().env;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
// Create two additional column families
|
||||
CreateColumnFamilies({"cf1", "cf2"}, options);
|
||||
ReopenWithColumnFamilies({"default", "cf1", "cf2"}, options);
|
||||
|
||||
// Verify initial state - auto compaction should be disabled
|
||||
ASSERT_TRUE(dbfull()->GetOptions(handles_[0]).disable_auto_compactions);
|
||||
ASSERT_TRUE(dbfull()->GetOptions(handles_[1]).disable_auto_compactions);
|
||||
ASSERT_TRUE(dbfull()->GetOptions(handles_[2]).disable_auto_compactions);
|
||||
|
||||
// Set options on multiple column families at once
|
||||
ASSERT_OK(dbfull()->SetOptions({handles_[1], handles_[2]},
|
||||
{{"disable_auto_compactions", "false"}}));
|
||||
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetOptions(handles_[0]).disable_auto_compactions); // unchanged
|
||||
ASSERT_FALSE(
|
||||
dbfull()->GetOptions(handles_[1]).disable_auto_compactions); // changed
|
||||
ASSERT_FALSE(
|
||||
dbfull()->GetOptions(handles_[2]).disable_auto_compactions); // changed
|
||||
|
||||
std::unordered_map<ColumnFamilyHandle*,
|
||||
std::unordered_map<std::string, std::string>>
|
||||
options_map;
|
||||
options_map[handles_[0]] = {{"disable_auto_compactions", "false"}};
|
||||
options_map[handles_[1]] = {{"disable_auto_compactions", "true"}};
|
||||
options_map[handles_[2]] = {{"disable_auto_compactions", "true"}};
|
||||
ASSERT_OK(dbfull()->SetOptions(options_map));
|
||||
|
||||
ASSERT_FALSE(dbfull()->GetOptions(handles_[0]).disable_auto_compactions);
|
||||
ASSERT_TRUE(dbfull()->GetOptions(handles_[1]).disable_auto_compactions);
|
||||
ASSERT_TRUE(dbfull()->GetOptions(handles_[2]).disable_auto_compactions);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+29
-10
@@ -1519,16 +1519,14 @@ TEST_F(DBPropertiesTest, NeedCompactHintPersistentTest) {
|
||||
|
||||
// Excluded from RocksDB lite tests due to `GetPropertiesOfAllTables()` usage.
|
||||
TEST_F(DBPropertiesTest, BlockAddForCompressionSampling) {
|
||||
// Sampled compression requires at least one of the following four types.
|
||||
if (!Snappy_Supported() && !Zlib_Supported() && !LZ4_Supported() &&
|
||||
!ZSTD_Supported()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.table_properties_collector_factories.emplace_back(
|
||||
std::make_shared<BlockCountingTablePropertiesCollectorFactory>());
|
||||
options.compression = kNoCompression;
|
||||
|
||||
bool fast_sampling_supported = Snappy_Supported() || LZ4_Supported();
|
||||
bool slow_sampling_supported = ZSTD_Supported() || Zlib_Supported();
|
||||
|
||||
for (bool sample_for_compression : {false, true}) {
|
||||
// For simplicity/determinism, sample 100% when enabled, or 0% when disabled
|
||||
@@ -1542,10 +1540,11 @@ TEST_F(DBPropertiesTest, BlockAddForCompressionSampling) {
|
||||
// L1_0 ["a", "b"]
|
||||
//
|
||||
// L0_0 was created by flush. L1_0 was created by compaction. Each file
|
||||
// contains one data block.
|
||||
// contains one data block with enough data to be compressible.
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put("a", "val"));
|
||||
ASSERT_OK(Put("b", "val"));
|
||||
for (int j = 0; j < 50; ++j) {
|
||||
ASSERT_OK(Put(std::to_string(j), "thisismyvalue"));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
if (i == 1) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
@@ -1558,13 +1557,33 @@ TEST_F(DBPropertiesTest, BlockAddForCompressionSampling) {
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&file_to_props));
|
||||
ASSERT_EQ(2, file_to_props.size());
|
||||
for (const auto& file_and_props : file_to_props) {
|
||||
auto& user_props = file_and_props.second->user_collected_properties;
|
||||
auto& props = *file_and_props.second;
|
||||
auto& user_props = props.user_collected_properties;
|
||||
ASSERT_TRUE(user_props.find(BlockCountingTablePropertiesCollector::
|
||||
kNumSampledBlocksPropertyName) !=
|
||||
user_props.end());
|
||||
ASSERT_EQ(user_props.at(BlockCountingTablePropertiesCollector::
|
||||
kNumSampledBlocksPropertyName),
|
||||
std::to_string(sample_for_compression ? 1 : 0));
|
||||
if (sample_for_compression) {
|
||||
EXPECT_GT(props.fast_compression_estimated_data_size, 0);
|
||||
EXPECT_GT(props.slow_compression_estimated_data_size, 0);
|
||||
if (fast_sampling_supported) {
|
||||
EXPECT_LT(props.fast_compression_estimated_data_size,
|
||||
props.data_size);
|
||||
if (slow_sampling_supported) {
|
||||
EXPECT_LT(props.slow_compression_estimated_data_size,
|
||||
props.fast_compression_estimated_data_size);
|
||||
}
|
||||
}
|
||||
if (slow_sampling_supported) {
|
||||
EXPECT_LT(props.slow_compression_estimated_data_size,
|
||||
props.data_size);
|
||||
}
|
||||
} else {
|
||||
EXPECT_EQ(props.fast_compression_estimated_data_size, 0);
|
||||
EXPECT_EQ(props.slow_compression_estimated_data_size, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
@@ -6541,6 +6544,9 @@ TEST_F(DBTest2, LastLevelStatistics) {
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
get_iostats_context()->Reset();
|
||||
IOStatsContext* iostats = get_iostats_context();
|
||||
|
||||
// generate 1 sst on level 0
|
||||
ASSERT_OK(Put("foo1", "bar"));
|
||||
ASSERT_OK(Put("bar", "bar"));
|
||||
@@ -6641,9 +6647,87 @@ TEST_F(DBTest2, LastLevelStatistics) {
|
||||
// Control
|
||||
ASSERT_NE(options.statistics->getTickerCount(LAST_LEVEL_READ_COUNT),
|
||||
options.statistics->getTickerCount(NON_LAST_LEVEL_READ_COUNT));
|
||||
|
||||
// Control: unknown temperature iostats should be zero since files have
|
||||
// explicit temperatures (mapped or written)
|
||||
EXPECT_EQ(
|
||||
iostats->file_io_stats_by_temperature.unknown_non_last_level_bytes_read,
|
||||
0);
|
||||
EXPECT_EQ(
|
||||
iostats->file_io_stats_by_temperature.unknown_non_last_level_read_count,
|
||||
0);
|
||||
EXPECT_EQ(
|
||||
iostats->file_io_stats_by_temperature.unknown_last_level_bytes_read, 0);
|
||||
EXPECT_EQ(
|
||||
iostats->file_io_stats_by_temperature.unknown_last_level_read_count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test the iostats for files with Temperature::kUnknown that is not mapped
|
||||
// to another temperature. These stats are used to indicate which non-tiered
|
||||
// workloads are most promising for tiering (so this test doesn't set
|
||||
// temperatures).
|
||||
TEST_F(DBTest2, UnknownLastLevelStatistics) {
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.no_block_cache = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
get_iostats_context()->Reset();
|
||||
IOStatsContext* iostats = get_iostats_context();
|
||||
|
||||
// Generate 1 sst file on level 0 with kUnknown temperature
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Read from the kUnknown file on non-last level
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Verify unknown_non_last_level stats are populated
|
||||
EXPECT_GT(
|
||||
iostats->file_io_stats_by_temperature.unknown_non_last_level_bytes_read,
|
||||
0);
|
||||
EXPECT_GT(
|
||||
iostats->file_io_stats_by_temperature.unknown_non_last_level_read_count,
|
||||
0);
|
||||
// No reads from last level yet
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.unknown_last_level_bytes_read,
|
||||
0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.unknown_last_level_read_count,
|
||||
0);
|
||||
|
||||
// Compact to the last level (level 6) explicitly using MoveFilesToLevel
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
MoveFilesToLevel(6);
|
||||
|
||||
// Reopen DB to ensure table cache is cleared and files are re-opened
|
||||
// with correct is_last_level flag
|
||||
Reopen(options);
|
||||
|
||||
// Reset iostats to measure only the following reads
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
// Read from the file now on last level (still kUnknown since
|
||||
// last_level_temperature is not set)
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Verify unknown_last_level stats are populated
|
||||
EXPECT_GT(iostats->file_io_stats_by_temperature.unknown_last_level_bytes_read,
|
||||
0);
|
||||
EXPECT_GT(iostats->file_io_stats_by_temperature.unknown_last_level_read_count,
|
||||
0);
|
||||
// No new reads from non-last level
|
||||
EXPECT_EQ(
|
||||
iostats->file_io_stats_by_temperature.unknown_non_last_level_bytes_read,
|
||||
0);
|
||||
EXPECT_EQ(
|
||||
iostats->file_io_stats_by_temperature.unknown_non_last_level_read_count,
|
||||
0);
|
||||
}
|
||||
|
||||
TEST_F(DBTest2, CheckpointFileTemperature) {
|
||||
class NoLinkTestFS : public FileTemperatureTestFS {
|
||||
using FileTemperatureTestFS::FileTemperatureTestFS;
|
||||
|
||||
+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;
|
||||
|
||||
+4
-3
@@ -1613,7 +1613,7 @@ TEST_F(DBWALTest, SyncWalPartialFailure) {
|
||||
return s;
|
||||
}
|
||||
|
||||
AcqRelAtomic<uint32_t> syncs_before_failure_{UINT32_MAX};
|
||||
Atomic<uint32_t> syncs_before_failure_{UINT32_MAX};
|
||||
|
||||
protected:
|
||||
class MyTestWritableFile : public FSWritableFileOwnerWrapper {
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -7,9 +7,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/sst_file_reader.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -48,6 +52,122 @@ class TimestampCompatibleCompactionTest : public DBTestBase {
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// Helper to get all files with their level and timestamps
|
||||
std::vector<std::tuple<int, std::string, std::string>>
|
||||
GetAllFileTimestamps() {
|
||||
std::vector<std::tuple<int, std::string, std::string>> results;
|
||||
ColumnFamilyHandle* cfh = db_->DefaultColumnFamily();
|
||||
auto* cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
|
||||
auto* vstorage = cfd->current()->storage_info();
|
||||
|
||||
for (int level = 0; level < cfd->NumberLevels(); level++) {
|
||||
for (auto* file : vstorage->LevelFiles(level)) {
|
||||
results.emplace_back(level, file->min_timestamp, file->max_timestamp);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// Helper to compute overall min/max timestamps across all files
|
||||
// Returns {min_ts, max_ts} as uint64_t values
|
||||
// Asserts that all files have non-empty timestamps
|
||||
std::pair<uint64_t, uint64_t> GetOverallTimestampRange() {
|
||||
auto files = GetAllFileTimestamps();
|
||||
EXPECT_GE(files.size(), 1U);
|
||||
|
||||
uint64_t overall_min = UINT64_MAX;
|
||||
uint64_t overall_max = 0;
|
||||
for (const auto& [level, min_ts, max_ts] : files) {
|
||||
EXPECT_FALSE(min_ts.empty()) << "min_timestamp empty at level " << level;
|
||||
EXPECT_FALSE(max_ts.empty()) << "max_timestamp empty at level " << level;
|
||||
|
||||
if (!min_ts.empty() && !max_ts.empty()) {
|
||||
uint64_t file_min = DecodeFixed64(min_ts.data());
|
||||
uint64_t file_max = DecodeFixed64(max_ts.data());
|
||||
overall_min = std::min(overall_min, file_min);
|
||||
overall_max = std::max(overall_max, file_max);
|
||||
}
|
||||
}
|
||||
return {overall_min, overall_max};
|
||||
}
|
||||
|
||||
// Helper to verify timestamp range matches expected values, including after
|
||||
// reopen
|
||||
void VerifyTimestampRangeWithPersistence(const Options& options,
|
||||
uint64_t expected_min,
|
||||
uint64_t expected_max) {
|
||||
// Verify before reopen
|
||||
auto [min_ts, max_ts] = GetOverallTimestampRange();
|
||||
ASSERT_EQ(expected_min, min_ts);
|
||||
ASSERT_EQ(expected_max, max_ts);
|
||||
|
||||
size_t file_count_before = GetAllFileTimestamps().size();
|
||||
|
||||
// Verify manifest persistence by reopening
|
||||
Reopen(options);
|
||||
|
||||
// Verify after reopen
|
||||
auto [reopened_min_ts, reopened_max_ts] = GetOverallTimestampRange();
|
||||
ASSERT_EQ(expected_min, reopened_min_ts);
|
||||
ASSERT_EQ(expected_max, reopened_max_ts);
|
||||
ASSERT_EQ(file_count_before, GetAllFileTimestamps().size());
|
||||
}
|
||||
|
||||
// Helper to create common options for UDT tests with level compaction
|
||||
Options CreateTimestampOptions(bool disable_auto_compactions = false) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.num_levels = 4;
|
||||
options.persist_user_defined_timestamps = true;
|
||||
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
options.disable_auto_compactions = disable_auto_compactions;
|
||||
return options;
|
||||
}
|
||||
|
||||
// Helper to write test data with alternating timestamps in a range
|
||||
// Writes keys [start_key, end_key) with timestamps alternating between
|
||||
// min_ts and max_ts
|
||||
void WriteDataWithTimestampRange(int start_key, int end_key, uint64_t min_ts,
|
||||
uint64_t max_ts) {
|
||||
std::string ts_buf;
|
||||
for (int i = start_key; i < end_key; i++) {
|
||||
ts_buf.clear();
|
||||
uint64_t ts = (i % 2 == 0) ? min_ts : max_ts;
|
||||
PutFixed64(&ts_buf, ts);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), Key(i), ts_buf,
|
||||
"value" + std::to_string(i)));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to check if any file has the expected timestamp range
|
||||
bool HasFileWithTimestampRange(uint64_t expected_min, uint64_t expected_max) {
|
||||
auto file_timestamps = GetAllFileTimestamps();
|
||||
for (const auto& [level, min_ts, max_ts] : file_timestamps) {
|
||||
if (!min_ts.empty() && !max_ts.empty()) {
|
||||
uint64_t file_min = DecodeFixed64(min_ts.data());
|
||||
uint64_t file_max = DecodeFixed64(max_ts.data());
|
||||
if (file_min == expected_min && file_max == expected_max) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper to verify data is readable with a given timestamp
|
||||
void VerifyDataReadable(int key, const std::string& expected_value,
|
||||
uint64_t read_ts) {
|
||||
std::string value;
|
||||
std::string ts_buf;
|
||||
PutFixed64(&ts_buf, read_ts);
|
||||
ReadOptions read_opts;
|
||||
Slice ts_slice(ts_buf);
|
||||
read_opts.timestamp = &ts_slice;
|
||||
ASSERT_OK(db_->Get(read_opts, Key(key), &value));
|
||||
ASSERT_EQ(expected_value, value);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TimestampCompatibleCompactionTest, UserKeyCrossFileBoundary) {
|
||||
@@ -344,6 +464,385 @@ TEST_F(TimestampCompatibleCompactionTest, EmptyCompactionOutput) {
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
}
|
||||
|
||||
TEST_F(TimestampCompatibleCompactionTest, SeqnoZeroingWithUDT) {
|
||||
// This test validates that seqno is only zeroed when the timestamp is older
|
||||
// than full_history_ts_low_. Before the fix, seqno was incorrectly zeroed
|
||||
// even when UDT was enabled but timestamp wasn't old enough.
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.comparator = test::BytewiseComparatorWithU64TsWrapper();
|
||||
options.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Track seqno zeroing events and which keys are zeroed
|
||||
std::set<std::string> zeroed_keys;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionIterator::PrepareOutput:ZeroingSeq", [&](void* arg) {
|
||||
auto* ikey = static_cast<ParsedInternalKey*>(arg);
|
||||
ASSERT_EQ(0, ikey->sequence);
|
||||
// Extract user key without timestamp (last 8 bytes)
|
||||
Slice user_key_with_ts = ikey->user_key;
|
||||
std::string user_key =
|
||||
user_key_with_ts.ToString().substr(0, user_key_with_ts.size() - 8);
|
||||
zeroed_keys.insert(user_key);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Case 1: Test that seqno is NOT zeroed when full_history_ts_low is not set
|
||||
// Write a key with timestamp 100
|
||||
std::string ts_str = Timestamp(100);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key1", ts_str, "value1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
zeroed_keys.clear();
|
||||
{
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
}
|
||||
// With UDT enabled and no full_history_ts_low, seqno should NOT be zeroed
|
||||
ASSERT_TRUE(zeroed_keys.empty());
|
||||
|
||||
// Case 2: Test that seqno IS zeroed when timestamp < full_history_ts_low
|
||||
// Write a new key with timestamp 200
|
||||
ts_str = Timestamp(200);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key2", ts_str, "value2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
zeroed_keys.clear();
|
||||
{
|
||||
// Set full_history_ts_low to 300, so ts < 300 should be zeroed
|
||||
std::string full_history_ts_low = Timestamp(300);
|
||||
Slice ts_slice = full_history_ts_low;
|
||||
CompactRangeOptions cro;
|
||||
cro.full_history_ts_low = &ts_slice;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
}
|
||||
// key1 (ts=100) and key2 (ts=200) both have ts < 300, so both should be
|
||||
// zeroed
|
||||
ASSERT_EQ(2u, zeroed_keys.size());
|
||||
ASSERT_TRUE(zeroed_keys.count("key1") > 0);
|
||||
ASSERT_TRUE(zeroed_keys.count("key2") > 0);
|
||||
|
||||
// Case 3: Write a new key with timestamp >= full_history_ts_low
|
||||
// and verify it is NOT zeroed while old keys are re-zeroed
|
||||
ts_str = Timestamp(500);
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key3", ts_str, "value3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
zeroed_keys.clear();
|
||||
{
|
||||
// Set full_history_ts_low to 400
|
||||
// key1 (ts=100) and key2 (ts=200) have ts < 400, will be re-processed
|
||||
// key3 (ts=500) has ts >= 400, should NOT be zeroed
|
||||
std::string full_history_ts_low = Timestamp(400);
|
||||
Slice ts_slice = full_history_ts_low;
|
||||
CompactRangeOptions cro;
|
||||
cro.full_history_ts_low = &ts_slice;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
}
|
||||
// key3 should NOT appear in zeroed_keys since ts=500 >= 400
|
||||
ASSERT_TRUE(zeroed_keys.count("key3") == 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// Verify data is still readable
|
||||
std::string value;
|
||||
ts_str = Timestamp(600);
|
||||
Slice read_ts = ts_str;
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts;
|
||||
ASSERT_OK(db_->Get(read_opts, "key1", &value));
|
||||
ASSERT_EQ("value1", value);
|
||||
ASSERT_OK(db_->Get(read_opts, "key2", &value));
|
||||
ASSERT_EQ("value2", value);
|
||||
ASSERT_OK(db_->Get(read_opts, "key3", &value));
|
||||
ASSERT_EQ("value3", value);
|
||||
}
|
||||
|
||||
// Test that files with max_timestamp >= full_history_ts_low are not marked
|
||||
// for bottommost compaction, which prevents infinite compaction loops.
|
||||
TEST_F(TimestampCompatibleCompactionTest,
|
||||
BottommostCompactionRespectsFullHistoryTsLow) {
|
||||
Options options = CreateTimestampOptions();
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write some data with timestamps 100-199
|
||||
std::string ts_buf;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ts_buf.clear();
|
||||
PutFixed64(&ts_buf, 100 + i);
|
||||
ASSERT_OK(
|
||||
db_->Put(WriteOptions(), Key(i), ts_buf, "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Compact to the bottommost level
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
// Set full_history_ts_low to 150 - files with max_ts >= 150 should NOT be
|
||||
// marked for bottommost compaction since seqno cannot be zeroed
|
||||
ts_buf.clear();
|
||||
PutFixed64(&ts_buf, 150);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), ts_buf));
|
||||
|
||||
// Release a snapshot to potentially trigger bottommost file marking
|
||||
// but files should NOT be marked because max_ts (199) >= full_history_ts_low
|
||||
// (150)
|
||||
const Snapshot* snap = db_->GetSnapshot();
|
||||
db_->ReleaseSnapshot(snap);
|
||||
|
||||
// Wait for any scheduled compactions - should complete without infinite loop
|
||||
// Use a reasonable timeout to detect infinite loops
|
||||
WaitForCompactOptions wfc_options;
|
||||
wfc_options.timeout = std::chrono::microseconds(5000000); // 5 seconds
|
||||
Status s = dbfull()->WaitForCompact(wfc_options);
|
||||
// Should succeed without timeout (no infinite compaction loop)
|
||||
ASSERT_TRUE(s.ok() || s.IsTimedOut());
|
||||
if (s.IsTimedOut()) {
|
||||
// If timeout, the fix is not working - this should not happen
|
||||
FAIL() << "WaitForCompact timed out - possible infinite compaction loop";
|
||||
}
|
||||
|
||||
// Now set full_history_ts_low beyond max timestamp in the file (200+)
|
||||
// This should allow the file to be properly marked and compacted
|
||||
ts_buf.clear();
|
||||
PutFixed64(&ts_buf, 300);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), ts_buf));
|
||||
|
||||
// Trigger another snapshot release to potentially mark files
|
||||
snap = db_->GetSnapshot();
|
||||
db_->ReleaseSnapshot(snap);
|
||||
|
||||
// Now compaction should clean up the file.
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
|
||||
// Test that files are NOT marked for bottommost compaction when UDT is enabled
|
||||
// and full_history_ts_low has never been set (empty).
|
||||
TEST_F(TimestampCompatibleCompactionTest,
|
||||
BottommostCompactionSkipsWhenFullHistoryTsLowNotSet) {
|
||||
Options options = CreateTimestampOptions();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Write some data with timestamps 100-199
|
||||
std::string ts_buf;
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ts_buf.clear();
|
||||
PutFixed64(&ts_buf, 100 + i);
|
||||
ASSERT_OK(
|
||||
db_->Put(WriteOptions(), Key(i), ts_buf, "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Compact to the bottommost level without setting full_history_ts_low
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
// Verify files have valid max_timestamp
|
||||
auto file_timestamps = GetAllFileTimestamps();
|
||||
ASSERT_GE(file_timestamps.size(), 1U);
|
||||
for (const auto& [level, min_ts, max_ts] : file_timestamps) {
|
||||
ASSERT_FALSE(max_ts.empty()) << "max_timestamp should not be empty";
|
||||
}
|
||||
|
||||
// full_history_ts_low is NOT set (empty), so files should NOT be marked
|
||||
// for bottommost compaction even after releasing a snapshot.
|
||||
// This tests the branch: if (full_history_ts_low.empty()) { continue; }
|
||||
const Snapshot* snap = db_->GetSnapshot();
|
||||
db_->ReleaseSnapshot(snap);
|
||||
|
||||
// Wait for any scheduled compactions
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Now set full_history_ts_low to a value > max_timestamp (199) in the file
|
||||
// This should allow the file to be properly marked and compacted
|
||||
ts_buf.clear();
|
||||
PutFixed64(&ts_buf, 300);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(db_->DefaultColumnFamily(), ts_buf));
|
||||
|
||||
// Trigger another snapshot release to potentially mark files
|
||||
snap = db_->GetSnapshot();
|
||||
db_->ReleaseSnapshot(snap);
|
||||
|
||||
// Now compaction should be able to proceed since full_history_ts_low is set
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Verify data is still readable
|
||||
VerifyDataReadable(0, "value0", 250);
|
||||
}
|
||||
|
||||
// Test that ingested SST files created with UDT have their min/max timestamps
|
||||
// properly extracted from table properties and populated in FileMetaData.
|
||||
// This verifies the fix in external_sst_file_ingestion_job.cc that calls
|
||||
// ExtractTimestampFromTableProperties after creating FileMetaData.
|
||||
TEST_F(TimestampCompatibleCompactionTest,
|
||||
IngestedFileTimestampsExtractedFromTableProperties) {
|
||||
Options options = CreateTimestampOptions();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create an SST file WITH timestamps using SstFileWriter
|
||||
std::string sst_file = dbname_ + "/ingested_udt_file.sst";
|
||||
const uint64_t kMinTs = 100;
|
||||
const uint64_t kMaxTs = 200;
|
||||
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
ASSERT_OK(sst_file_writer.Open(sst_file));
|
||||
|
||||
std::string ts_buf;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// Alternate between min and max timestamps
|
||||
uint64_t ts = (i % 2 == 0) ? kMinTs : kMaxTs;
|
||||
ts_buf.clear();
|
||||
PutFixed64(&ts_buf, ts);
|
||||
// SstFileWriter with UDT comparator requires key with timestamp
|
||||
ASSERT_OK(
|
||||
sst_file_writer.Put(Key(i), ts_buf, "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
}
|
||||
|
||||
// Verify the SST file has timestamp properties before ingestion
|
||||
{
|
||||
std::unique_ptr<SstFileReader> reader(new SstFileReader(options));
|
||||
ASSERT_OK(reader->Open(sst_file));
|
||||
auto props = reader->GetTableProperties();
|
||||
auto& user_collected = props->user_collected_properties;
|
||||
ASSERT_TRUE(user_collected.find("rocksdb.timestamp_min") !=
|
||||
user_collected.end())
|
||||
<< "SST file should have rocksdb.timestamp_min property";
|
||||
ASSERT_TRUE(user_collected.find("rocksdb.timestamp_max") !=
|
||||
user_collected.end())
|
||||
<< "SST file should have rocksdb.timestamp_max property";
|
||||
}
|
||||
|
||||
// Ingest the SST file
|
||||
IngestExternalFileOptions ifo;
|
||||
ifo.move_files = false;
|
||||
ASSERT_OK(db_->IngestExternalFile({sst_file}, ifo));
|
||||
|
||||
// Verify the ingested file has proper timestamps in FileMetaData
|
||||
ASSERT_TRUE(HasFileWithTimestampRange(kMinTs, kMaxTs))
|
||||
<< "Ingested file should have min_timestamp=" << kMinTs
|
||||
<< " and max_timestamp=" << kMaxTs << " in FileMetaData";
|
||||
|
||||
// Verify timestamps persist after reopen
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_TRUE(HasFileWithTimestampRange(kMinTs, kMaxTs))
|
||||
<< "Ingested file timestamps should persist after reopen";
|
||||
|
||||
// Verify data is readable
|
||||
VerifyDataReadable(0, "value0", kMaxTs);
|
||||
|
||||
// Clean up
|
||||
ASSERT_OK(env_->DeleteFile(sst_file));
|
||||
}
|
||||
|
||||
// Test that min/max timestamps are correctly tracked in FileMetaData and
|
||||
// persisted in the manifest during flush.
|
||||
TEST_F(TimestampCompatibleCompactionTest, TimestampRangePersistenceFlush) {
|
||||
Options options = CreateTimestampOptions();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Expected timestamp range
|
||||
const uint64_t kMinTs = 100;
|
||||
const uint64_t kMaxTs = 200;
|
||||
|
||||
// Write data with specific timestamp range
|
||||
WriteDataWithTimestampRange(0, 50, kMinTs, kMaxTs);
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// First verify table properties have the timestamps
|
||||
// (this confirms TimestampTablePropertiesCollector is working)
|
||||
TablePropertiesCollection props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
|
||||
ASSERT_EQ(1U, props.size());
|
||||
for (const auto& item : props) {
|
||||
auto& user_collected = item.second->user_collected_properties;
|
||||
ASSERT_TRUE(user_collected.find("rocksdb.timestamp_min") !=
|
||||
user_collected.end());
|
||||
ASSERT_TRUE(user_collected.find("rocksdb.timestamp_max") !=
|
||||
user_collected.end());
|
||||
// Verify the collected timestamps match expected values
|
||||
std::string collected_min_ts = user_collected.at("rocksdb.timestamp_min");
|
||||
std::string collected_max_ts = user_collected.at("rocksdb.timestamp_max");
|
||||
ASSERT_EQ(kMinTs, DecodeFixed64(collected_min_ts.data()));
|
||||
ASSERT_EQ(kMaxTs, DecodeFixed64(collected_max_ts.data()));
|
||||
}
|
||||
|
||||
// Verify FileMetaData timestamps and persistence through reopen
|
||||
VerifyTimestampRangeWithPersistence(options, kMinTs, kMaxTs);
|
||||
|
||||
// Verify we can still read the data
|
||||
VerifyDataReadable(0, "value0", kMaxTs);
|
||||
}
|
||||
|
||||
// Test that min/max timestamps are correctly merged during compaction
|
||||
// and persisted in the manifest.
|
||||
TEST_F(TimestampCompatibleCompactionTest, TimestampRangePersistenceCompaction) {
|
||||
Options options = CreateTimestampOptions(true /* disable_auto_compactions */);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create multiple L0 files with different timestamp ranges
|
||||
// File 1: timestamps 100-150
|
||||
const uint64_t kFile1MinTs = 100;
|
||||
const uint64_t kFile1MaxTs = 150;
|
||||
WriteDataWithTimestampRange(0, 10, kFile1MinTs, kFile1MaxTs);
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// File 2: timestamps 50-80 (earlier range)
|
||||
const uint64_t kFile2MinTs = 50;
|
||||
const uint64_t kFile2MaxTs = 80;
|
||||
WriteDataWithTimestampRange(10, 20, kFile2MinTs, kFile2MaxTs);
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// File 3: timestamps 200-300 (later range)
|
||||
const uint64_t kFile3MinTs = 200;
|
||||
const uint64_t kFile3MaxTs = 300;
|
||||
WriteDataWithTimestampRange(20, 30, kFile3MinTs, kFile3MaxTs);
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Expected combined range: min=50, max=300
|
||||
const uint64_t kExpectedMinTs = 50;
|
||||
const uint64_t kExpectedMaxTs = 300;
|
||||
|
||||
// Verify we have 3 L0 files before compaction with valid timestamps
|
||||
auto files_before = GetAllFileTimestamps();
|
||||
ASSERT_EQ(3U, files_before.size());
|
||||
for (const auto& [level, min_ts, max_ts] : files_before) {
|
||||
ASSERT_EQ(0, level); // All files should be in L0
|
||||
ASSERT_FALSE(min_ts.empty());
|
||||
ASSERT_FALSE(max_ts.empty());
|
||||
}
|
||||
|
||||
// Trigger compaction
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Verify timestamp range and persistence through reopen
|
||||
VerifyTimestampRangeWithPersistence(options, kExpectedMinTs, kExpectedMaxTs);
|
||||
|
||||
// Verify data is still readable
|
||||
VerifyDataReadable(0, "value0", kExpectedMaxTs);
|
||||
VerifyDataReadable(15, "value15", kExpectedMaxTs);
|
||||
VerifyDataReadable(25, "value25", kExpectedMaxTs);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+3
-3
@@ -741,7 +741,7 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
|
||||
ExternalSstFileInfo external_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&external_info));
|
||||
}
|
||||
AcqRelAtomic<bool> parallel_ingest_completed{false};
|
||||
Atomic<bool> parallel_ingest_completed{false};
|
||||
port::Thread parallel_ingest{[&]() {
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
ingest_opts.move_files = true; // faster than copy
|
||||
@@ -750,7 +750,7 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
|
||||
parallel_ingest_completed.Store(true);
|
||||
}};
|
||||
|
||||
AcqRelAtomic<bool> flush_completed{false};
|
||||
Atomic<bool> flush_completed{false};
|
||||
port::Thread parallel_flush{[&]() {
|
||||
FlushOptions flush_opts;
|
||||
// NB: Flush with wait=false case is tested above in LockWALInEffect
|
||||
@@ -762,7 +762,7 @@ TEST_P(DBWriteTest, LockWALConcurrentRecursive) {
|
||||
flush_completed.Store(true);
|
||||
}};
|
||||
|
||||
AcqRelAtomic<bool> parallel_put_completed{false};
|
||||
Atomic<bool> parallel_put_completed{false};
|
||||
port::Thread parallel_put{[&]() {
|
||||
// This can make certain failure scenarios more likely:
|
||||
// sleep(1);
|
||||
|
||||
+11
-9
@@ -158,15 +158,17 @@ Status UpdateManifestForFilesState(
|
||||
// Current state inconsistent with manifest
|
||||
++files_updated;
|
||||
edit.DeleteFile(level, number);
|
||||
edit.AddFile(
|
||||
level, number, lf->fd.GetPathId(), lf->fd.GetFileSize(),
|
||||
lf->smallest, lf->largest, lf->fd.smallest_seqno,
|
||||
lf->fd.largest_seqno, lf->marked_for_compaction, temp,
|
||||
lf->oldest_blob_file_number, lf->oldest_ancester_time,
|
||||
lf->file_creation_time, lf->epoch_number, lf->file_checksum,
|
||||
lf->file_checksum_func_name, lf->unique_id,
|
||||
lf->compensated_range_deletion_size, lf->tail_size,
|
||||
lf->user_defined_timestamps_persisted);
|
||||
edit.AddFile(level, lf->fd.GetNumber(), lf->fd.GetPathId(),
|
||||
lf->fd.GetFileSize(), lf->smallest, lf->largest,
|
||||
lf->fd.smallest_seqno, lf->fd.largest_seqno,
|
||||
lf->marked_for_compaction, temp,
|
||||
lf->oldest_blob_file_number,
|
||||
lf->oldest_ancester_time, lf->file_creation_time,
|
||||
lf->epoch_number, lf->file_checksum,
|
||||
lf->file_checksum_func_name, lf->unique_id,
|
||||
lf->compensated_range_deletion_size, lf->tail_size,
|
||||
lf->user_defined_timestamps_persisted,
|
||||
lf->min_timestamp, lf->max_timestamp);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "file/file_util.h"
|
||||
@@ -299,6 +300,18 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
// ingestion.
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions ro;
|
||||
// Pass user-provided checksums through FileOptions when available.
|
||||
// The caller may not have provided checksums at all (empty vectors),
|
||||
// so we guard with a bounds check.
|
||||
FileOptions fopts;
|
||||
if (i < files_checksums.size()) {
|
||||
fopts.file_checksum = files_checksums[i];
|
||||
}
|
||||
if (i < files_checksum_func_names.size()) {
|
||||
fopts.file_checksum_func_name = files_checksum_func_names[i];
|
||||
} else {
|
||||
fopts.file_checksum_func_name = kNoFileChecksumFuncName;
|
||||
}
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_.get(), files_to_ingest_[i].internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(),
|
||||
@@ -307,7 +320,7 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads, io_tracer_,
|
||||
db_options_.rate_limiter.get(), ro, db_options_.stats,
|
||||
db_options_.clock);
|
||||
db_options_.clock, fopts);
|
||||
if (!io_s.ok()) {
|
||||
status = io_s;
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
@@ -699,9 +712,13 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
? kReservedEpochNumberForFileIngestedBehind
|
||||
: cfd_->NewEpochNumber(), // orders files ingested to L0
|
||||
file->file_checksum, file->file_checksum_func_name, file->unique_id, 0,
|
||||
tail_size, file->user_defined_timestamps_persisted);
|
||||
tail_size, file->user_defined_timestamps_persisted, "", "");
|
||||
f_metadata.temperature = file->file_temperature;
|
||||
f_metadata.marked_for_compaction = marked_for_compaction;
|
||||
// Extract min/max timestamps from table properties for UDT support.
|
||||
// This ensures ingested files have proper timestamp ranges in FileMetaData,
|
||||
// similar to files created by flush and compaction.
|
||||
ExtractTimestampFromTableProperties(file->table_properties, &f_metadata);
|
||||
edit_.AddFile(file->picked_level, f_metadata);
|
||||
|
||||
*batch_uppermost_level =
|
||||
@@ -1477,13 +1494,15 @@ IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
|
||||
// TODO: rate limit file reads for checksum calculation during file ingestion.
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions ro;
|
||||
FileOptions gen_fopts;
|
||||
gen_fopts.file_checksum_func_name = kNoFileChecksumFuncName;
|
||||
IOStatus io_s = GenerateOneFileChecksum(
|
||||
fs_.get(), file_to_ingest->internal_file_path,
|
||||
db_options_.file_checksum_gen_factory.get(), requested_checksum_func_name,
|
||||
&file_checksum, &file_checksum_func_name,
|
||||
ingestion_options_.verify_checksums_readahead_size,
|
||||
db_options_.allow_mmap_reads, io_tracer_, db_options_.rate_limiter.get(),
|
||||
ro, db_options_.stats, db_options_.clock);
|
||||
ro, db_options_.stats, db_options_.clock, gen_fopts);
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "Failed to generate checksum for %s: %s",
|
||||
|
||||
+2
-1
@@ -1095,7 +1095,8 @@ Status FlushJob::WriteLevel0Table() {
|
||||
meta_.file_creation_time, meta_.epoch_number,
|
||||
meta_.file_checksum, meta_.file_checksum_func_name,
|
||||
meta_.unique_id, meta_.compensated_range_deletion_size,
|
||||
meta_.tail_size, meta_.user_defined_timestamps_persisted);
|
||||
meta_.tail_size, meta_.user_defined_timestamps_persisted,
|
||||
meta_.min_timestamp, meta_.max_timestamp);
|
||||
edit_->SetBlobFileAdditions(std::move(blob_file_additions));
|
||||
}
|
||||
// Piggyback FlushJobInfo on the first first flushed memtable.
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -310,8 +310,10 @@ Status ImportColumnFamilyJob::GetIngestedFileInfo(
|
||||
std::unique_ptr<FSRandomAccessFile> sst_file;
|
||||
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
|
||||
|
||||
status =
|
||||
fs_->NewRandomAccessFile(external_file, env_options_, &sst_file, nullptr);
|
||||
FileOptions fo{env_options_};
|
||||
fo.file_checksum = file_meta.file_checksum;
|
||||
fo.file_checksum_func_name = file_meta.file_checksum_func_name;
|
||||
status = fs_->NewRandomAccessFile(external_file, fo, &sst_file, nullptr);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
+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) {
|
||||
|
||||
+12
-12
@@ -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),
|
||||
@@ -704,17 +704,17 @@ class Repairer {
|
||||
VersionEdit dummy_edit;
|
||||
for (const auto* table : cf_id_and_tables.second) {
|
||||
// TODO(opt): separate out into multiple levels
|
||||
const auto& meta = table->meta;
|
||||
dummy_edit.AddFile(
|
||||
0, table->meta.fd.GetNumber(), table->meta.fd.GetPathId(),
|
||||
table->meta.fd.GetFileSize(), table->meta.smallest,
|
||||
table->meta.largest, table->meta.fd.smallest_seqno,
|
||||
table->meta.fd.largest_seqno, table->meta.marked_for_compaction,
|
||||
table->meta.temperature, table->meta.oldest_blob_file_number,
|
||||
table->meta.oldest_ancester_time, table->meta.file_creation_time,
|
||||
table->meta.epoch_number, table->meta.file_checksum,
|
||||
table->meta.file_checksum_func_name, table->meta.unique_id,
|
||||
table->meta.compensated_range_deletion_size, table->meta.tail_size,
|
||||
table->meta.user_defined_timestamps_persisted);
|
||||
0, meta.fd.GetNumber(), meta.fd.GetPathId(), meta.fd.GetFileSize(),
|
||||
meta.smallest, meta.largest, meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.marked_for_compaction, meta.temperature,
|
||||
meta.oldest_blob_file_number, meta.oldest_ancester_time,
|
||||
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
|
||||
meta.file_checksum_func_name, meta.unique_id,
|
||||
meta.compensated_range_deletion_size, meta.tail_size,
|
||||
meta.user_defined_timestamps_persisted, meta.min_timestamp,
|
||||
meta.max_timestamp);
|
||||
}
|
||||
s = dummy_version_builder.Apply(&dummy_edit);
|
||||
if (s.ok()) {
|
||||
|
||||
+3
-2
@@ -98,6 +98,8 @@ Status TableCache::GetTableReader(
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
FileOptions fopts = file_options;
|
||||
fopts.temperature = file_temperature;
|
||||
fopts.file_checksum = file_meta.file_checksum;
|
||||
fopts.file_checksum_func_name = file_meta.file_checksum_func_name;
|
||||
Status s = PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
|
||||
TEST_SYNC_POINT_CALLBACK("TableCache::GetTableReader:BeforeOpenFile",
|
||||
const_cast<Status*>(&s));
|
||||
@@ -113,8 +115,7 @@ Status TableCache::GetTableReader(
|
||||
Status temp_s =
|
||||
PrepareIOFromReadOptions(ro, ioptions_.clock, fopts.io_options);
|
||||
if (temp_s.ok()) {
|
||||
temp_s = ioptions_.fs->NewRandomAccessFile(fname, file_options, &file,
|
||||
nullptr);
|
||||
temp_s = ioptions_.fs->NewRandomAccessFile(fname, fopts, &file, nullptr);
|
||||
}
|
||||
if (temp_s.ok()) {
|
||||
RecordTick(ioptions_.stats, NO_FILE_OPENS);
|
||||
|
||||
@@ -76,7 +76,8 @@ class VersionBuilderTest : public testing::Test {
|
||||
oldest_blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, epoch_number, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2, 0, 0,
|
||||
/* user_defined_timestamps_persisted */ true);
|
||||
/* user_defined_timestamps_persisted */ true, /* min timestamp */ "",
|
||||
/* max timestamp */ "");
|
||||
f->compensated_file_size = file_size;
|
||||
f->num_entries = num_entries;
|
||||
f->num_deletions = num_deletions;
|
||||
|
||||
@@ -304,6 +304,15 @@ void VersionEdit::EncodeToNewFile4(const FileMetaData& f, int level,
|
||||
char p = static_cast<char>(0);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
// Encode min/max timestamp if they are non-empty
|
||||
if (!f.min_timestamp.empty()) {
|
||||
PutVarint32(dst, NewFileCustomTag::kMinTimestamp);
|
||||
PutLengthPrefixedSlice(dst, Slice(f.min_timestamp));
|
||||
}
|
||||
if (!f.max_timestamp.empty()) {
|
||||
PutVarint32(dst, NewFileCustomTag::kMaxTimestamp);
|
||||
PutLengthPrefixedSlice(dst, Slice(f.max_timestamp));
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
|
||||
dst);
|
||||
|
||||
@@ -443,6 +452,12 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
|
||||
}
|
||||
f.user_defined_timestamps_persisted = (field[0] == 1);
|
||||
break;
|
||||
case kMinTimestamp:
|
||||
f.min_timestamp = field.ToString();
|
||||
break;
|
||||
case kMaxTimestamp:
|
||||
f.max_timestamp = field.ToString();
|
||||
break;
|
||||
default:
|
||||
if ((custom_tag & kCustomTagNonSafeIgnoreMask) != 0) {
|
||||
// Should not proceed if cannot understand it
|
||||
|
||||
+33
-15
@@ -276,6 +276,14 @@ struct FileMetaData {
|
||||
// false, it's explicitly written to Manifest.
|
||||
bool user_defined_timestamps_persisted = true;
|
||||
|
||||
// Minimum user-defined timestamp in the file. Empty if no UDT or unknown.
|
||||
// This is populated from the table properties "rocksdb.timestamp_min".
|
||||
std::string min_timestamp;
|
||||
|
||||
// Maximum user-defined timestamp in the file. Empty if no UDT or unknown.
|
||||
// This is populated from the table properties "rocksdb.timestamp_max".
|
||||
std::string max_timestamp;
|
||||
|
||||
FileMetaData() = default;
|
||||
|
||||
FileMetaData(uint64_t file, uint32_t file_path_id, uint64_t file_size,
|
||||
@@ -288,7 +296,9 @@ struct FileMetaData {
|
||||
const std::string& _file_checksum_func_name,
|
||||
UniqueId64x2 _unique_id,
|
||||
const uint64_t _compensated_range_deletion_size,
|
||||
uint64_t _tail_size, bool _user_defined_timestamps_persisted)
|
||||
uint64_t _tail_size, bool _user_defined_timestamps_persisted,
|
||||
const std::string& _min_timestamp,
|
||||
const std::string& _max_timestamp)
|
||||
: fd(file, file_path_id, file_size, smallest_seq, largest_seq),
|
||||
smallest(smallest_key),
|
||||
largest(largest_key),
|
||||
@@ -303,7 +313,9 @@ struct FileMetaData {
|
||||
file_checksum_func_name(_file_checksum_func_name),
|
||||
unique_id(std::move(_unique_id)),
|
||||
tail_size(_tail_size),
|
||||
user_defined_timestamps_persisted(_user_defined_timestamps_persisted) {
|
||||
user_defined_timestamps_persisted(_user_defined_timestamps_persisted),
|
||||
min_timestamp(_min_timestamp),
|
||||
max_timestamp(_max_timestamp) {
|
||||
TEST_SYNC_POINT_CALLBACK("FileMetaData::FileMetaData", this);
|
||||
}
|
||||
|
||||
@@ -386,7 +398,8 @@ struct FileMetaData {
|
||||
usage += sizeof(*this);
|
||||
#endif // ROCKSDB_MALLOC_USABLE_SIZE
|
||||
usage += smallest.size() + largest.size() + file_checksum.size() +
|
||||
file_checksum_func_name.size();
|
||||
file_checksum_func_name.size() + min_timestamp.size() +
|
||||
max_timestamp.size();
|
||||
return usage;
|
||||
}
|
||||
|
||||
@@ -557,16 +570,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 */);
|
||||
}
|
||||
@@ -734,17 +750,19 @@ class VersionEdit {
|
||||
const std::string& file_checksum_func_name,
|
||||
const UniqueId64x2& unique_id,
|
||||
const uint64_t compensated_range_deletion_size,
|
||||
uint64_t tail_size, bool user_defined_timestamps_persisted) {
|
||||
uint64_t tail_size, bool user_defined_timestamps_persisted,
|
||||
const std::string& min_timestamp = "",
|
||||
const std::string& max_timestamp = "") {
|
||||
assert(smallest_seqno <= largest_seqno);
|
||||
new_files_.emplace_back(
|
||||
level,
|
||||
FileMetaData(file, file_path_id, file_size, smallest, largest,
|
||||
smallest_seqno, largest_seqno, marked_for_compaction,
|
||||
temperature, oldest_blob_file_number, oldest_ancester_time,
|
||||
file_creation_time, epoch_number, file_checksum,
|
||||
file_checksum_func_name, unique_id,
|
||||
compensated_range_deletion_size, tail_size,
|
||||
user_defined_timestamps_persisted));
|
||||
FileMetaData(
|
||||
file, file_path_id, file_size, smallest, largest, smallest_seqno,
|
||||
largest_seqno, marked_for_compaction, temperature,
|
||||
oldest_blob_file_number, oldest_ancester_time, file_creation_time,
|
||||
epoch_number, file_checksum, file_checksum_func_name, unique_id,
|
||||
compensated_range_deletion_size, tail_size,
|
||||
user_defined_timestamps_persisted, min_timestamp, max_timestamp));
|
||||
files_to_quarantine_.push_back(file);
|
||||
if (!HasLastSequence() || largest_seqno > GetLastSequence()) {
|
||||
SetLastSequence(largest_seqno);
|
||||
|
||||
Loaded 100 of 264 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user