mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 11:32:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fae8a27fbd | ||
|
|
e12650878e | ||
|
|
46c255cc87 | ||
|
|
1b2aa8958b | ||
|
|
fa143aa4db | ||
|
|
6ea50e6054 | ||
|
|
17f7a626a8 |
No files matched your search
@@ -1,17 +1,7 @@
|
||||
name: build-folly
|
||||
description: Build folly and dependencies (skipped if cache hit)
|
||||
inputs:
|
||||
cache-hit:
|
||||
description: Whether the folly cache was hit
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Build folly and dependencies
|
||||
if: ${{ inputs.cache-hit != 'true' }}
|
||||
run: make build_folly
|
||||
shell: bash
|
||||
- name: Skip folly build (using cached version)
|
||||
if: ${{ inputs.cache-hit == 'true' }}
|
||||
run: echo "Folly build skipped - using cached version"
|
||||
shell: bash
|
||||
@@ -1,33 +0,0 @@
|
||||
name: cache-folly
|
||||
description: Cache folly build to speed up CI
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: Whether the cache was hit
|
||||
value: ${{ steps.cache-folly-build.outputs.cache-hit }}
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Extract FOLLY_MK_HASH
|
||||
id: extract-folly-hash
|
||||
shell: bash
|
||||
run: |
|
||||
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: ${{ steps.extract-folly-install-dir.outputs.dir }}
|
||||
# Key is based on:
|
||||
# - OS and architecture
|
||||
# - 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 }}
|
||||
@@ -3,9 +3,5 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout folly sources
|
||||
run: |
|
||||
make checkout_folly
|
||||
shell: bash
|
||||
- name: Install patchelf
|
||||
run: apt-get update -y && apt-get install -y patchelf
|
||||
run: make checkout_folly
|
||||
shell: bash
|
||||
@@ -4,16 +4,6 @@ runs:
|
||||
steps:
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.3.1
|
||||
- name: Cache ccache directory
|
||||
id: ccache-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: C:\a\rocksdb\rocksdb\.ccache
|
||||
key: rocksdb-build-${{ runner.os }}-${{ runner.arch }}-ccache-${{ hashFiles('CMakeLists.txt', 'cmake/**/*.cmake') }}-v1
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
max-size: "10GB"
|
||||
- name: Custom steps
|
||||
env:
|
||||
THIRDPARTY_HOME: ${{ github.workspace }}/thirdparty
|
||||
@@ -48,12 +38,11 @@ runs:
|
||||
$env:Path = $env:JAVA_HOME + ";" + $env:Path
|
||||
mkdir build
|
||||
cd build
|
||||
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DWIN_CI=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
|
||||
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
cd ..
|
||||
echo "Building with VS version: $Env:CMAKE_GENERATOR"
|
||||
# use more parallel processes than the number of processes available, as most of the compile command would be cache hit
|
||||
msbuild build/rocksdb.sln /m:32 /p:LinkIncremental=false -property:Configuration=Debug -property:Platform=x64
|
||||
msbuild build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
echo ========================= Test RocksDB =========================
|
||||
build_tools\run_ci_db_test.ps1 -SuiteRun arena_test,db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16
|
||||
@@ -63,7 +52,3 @@ runs:
|
||||
& ctest -C Debug -j 16
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
shell: pwsh
|
||||
- name: Show ccache stats
|
||||
shell: pwsh
|
||||
run: |
|
||||
ccache --show-stats -v
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
TEST_TMPDIR: "/tmp/rocksdb_test_tmp"
|
||||
@@ -41,16 +41,16 @@ jobs:
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: make V=1 -j32 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang-18-asan-ubsan-with-folly:
|
||||
build-linux-clang-13-asan-ubsan-with-folly:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: clang-18
|
||||
CXX: clang++-18
|
||||
CC: clang-13
|
||||
CXX: clang++-13
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -58,38 +58,17 @@ jobs:
|
||||
- uses: "./.github/actions/build-folly"
|
||||
- run: LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-cmake-with-folly:
|
||||
build-linux-valgrind:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
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 VERBOSE=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-release-with-folly:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: "DEBUG_LEVEL=0 make -j20 build_folly"
|
||||
- run: "USE_FOLLY=1 LIB_MODE=static DEBUG_LEVEL=0 V=1 make -j20 release"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 -DCMAKE_BUILD_TYPE=Release .. && make VERBOSE=1 -j20 && ctest -j20)"
|
||||
- run: make V=1 -j32 valgrind_test
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-windows-vs2022-avx2:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
@@ -110,26 +89,12 @@ 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:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -142,26 +107,41 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- name: Build rocksdb lib
|
||||
run: CC=clang-18 CXX=clang++-18 USE_CLANG=1 make -j4 static_lib
|
||||
run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j4 static_lib
|
||||
- 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:
|
||||
build-linux-gcc-11-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make VERBOSE=1 -j20 && ctest -j20)"
|
||||
- run: LIB_MODE=static CC=gcc-11 CXX=g++-11 V=1 make -j32 all microbench
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-cmake-with-folly-lite-no-test:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: gcc-10
|
||||
CXX: g++-10
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 .. && make V=1 -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -92,22 +92,38 @@ 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: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: gcc-10
|
||||
CXX: g++-10
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- uses: "./.github/actions/build-folly"
|
||||
- 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:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: gcc-10
|
||||
CXX: g++-10
|
||||
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: USE_FOLLY=1 LIB_MODE=static V=1 make -j32 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-make-with-folly-lite-no-test:
|
||||
@@ -115,50 +131,52 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: gcc-10
|
||||
CXX: g++-10
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: USE_FOLLY_LITE=1 EXTRA_CXXFLAGS=-DGLOG_USE_GLOG_EXPORT V=1 make -j32 all
|
||||
- run: USE_FOLLY_LITE=1 V=1 make -j32 all
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-cmake-with-folly-coroutines:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: gcc-10
|
||||
CXX: g++-10
|
||||
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_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make VERBOSE=1 -j20 && ctest -j20)"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_COROUTINES=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-cmake-with-benchmark-no-thread-status:
|
||||
build-linux-cmake-with-benchmark:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 -DCMAKE_CXX_FLAGS=-DNROCKSDB_THREAD_STATUS .. && make VERBOSE=1 -j20 && ctest -j20
|
||||
- run: mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-encrypted_env-no_compression:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -172,7 +190,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -193,61 +211,68 @@ jobs:
|
||||
- run: ls librocksdb.a
|
||||
- run: if ./trace_analyzer --version; then false; else true; fi
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang-13-no_test_run:
|
||||
build-linux-clang-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 8-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
# FIXME: get back to "all microbench" targets
|
||||
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 EXTRA_CXXFLAGS=-stdlib=libc++ EXTRA_LDFLAGS=-stdlib=libc++ make -j32 shared_lib
|
||||
- run: make clean
|
||||
# FIXME: get back to "release" target
|
||||
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 EXTRA_CXXFLAGS=-stdlib=libc++ EXTRA_LDFLAGS=-stdlib=libc++ DEBUG_LEVEL=0 make -j32 shared_lib
|
||||
- run: CC=clang CXX=clang++ USE_CLANG=1 PORTABLE=1 make V=1 -j16 all
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang-18-no_test_run:
|
||||
build-linux-clang-13-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: CC=clang-18 CXX=clang++-18 USE_CLANG=1 make -j32 all microbench
|
||||
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j32 all microbench
|
||||
- run: make clean
|
||||
- run: CC=clang-18 CXX=clang++-18 USE_CLANG=1 DEBUG_LEVEL=0 make -j32 release
|
||||
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 DEBUG_LEVEL=0 make -j32 release
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-gcc-14-no_test_run:
|
||||
build-linux-gcc-8-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: CC=gcc-14 CXX=g++-14 V=1 make -j32 all microbench
|
||||
- run: CC=gcc-8 CXX=g++-8 V=1 make -j32 all
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-gcc-10-cxx20-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: CC=gcc-10 CXX=g++-10 V=1 ROCKSDB_CXX_STANDARD=c++20 make -j32 all
|
||||
- uses: "./.github/actions/post-steps"
|
||||
|
||||
# ======================== Linux Other Checks ======================= #
|
||||
build-linux-clang18-clang-analyze:
|
||||
build-linux-clang10-clang-analyze:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-18" CLANG_SCAN_BUILD=scan-build-18 USE_CLANG=1 make V=1 -j32 analyze
|
||||
- run: CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-10" CLANG_SCAN_BUILD=scan-build-10 USE_CLANG=1 make V=1 -j32 analyze
|
||||
- uses: "./.github/actions/post-steps"
|
||||
- name: compress test report
|
||||
run: tar -cvzf scan_build_report.tar.gz scan_build_report
|
||||
@@ -275,7 +300,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -283,96 +308,90 @@ jobs:
|
||||
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=960 --max_key=2500000' blackbox_crash_test_with_atomic_flush
|
||||
- uses: "./.github/actions/post-steps"
|
||||
# ======================= Linux with Sanitizers ===================== #
|
||||
build-linux-clang18-asan-ubsan:
|
||||
build-linux-clang10-asan-ubsan:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 32-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: COMPILE_WITH_ASAN=1 COMPILE_WITH_UBSAN=1 CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j40 check
|
||||
- run: COMPILE_WITH_ASAN=1 COMPILE_WITH_UBSAN=1 CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j40 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang18-mini-tsan:
|
||||
build-linux-clang13-mini-tsan:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 32-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: COMPILE_WITH_TSAN=1 CC=clang-18 CXX=clang++-18 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check
|
||||
- run: COMPILE_WITH_TSAN=1 CC=clang-13 CXX=clang++-13 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-static_lib-alt_namespace-status_checked:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=static OPT="-DROCKSDB_USE_STD_SEMAPHORES -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j24 check
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=static OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j24 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
# ========================= MacOS build only ======================== #
|
||||
build-macos:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-15-xlarge
|
||||
runs-on: macos-13
|
||||
env:
|
||||
ROCKSDB_DISABLE_JEMALLOC: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: 16.4.0
|
||||
xcode-version: 14.3.1
|
||||
- uses: "./.github/actions/increase-max-open-files-on-macos"
|
||||
- uses: "./.github/actions/install-gflags-on-macos"
|
||||
- uses: "./.github/actions/pre-steps-macos"
|
||||
- name: Build
|
||||
run: ulimit -S -n `ulimit -H -n` && make V=1 J=16 -j8 all
|
||||
run: ulimit -S -n `ulimit -H -n` && make V=1 J=16 -j16 all
|
||||
- uses: "./.github/actions/post-steps"
|
||||
# ========================= MacOS with Tests ======================== #
|
||||
build-macos-cmake:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-15-xlarge
|
||||
runs-on: macos-13
|
||||
strategy:
|
||||
matrix:
|
||||
run_sharded_tests: [0, 1, 2, 3]
|
||||
run_even_tests: [true, false]
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: 16.4.0
|
||||
xcode-version: 14.3.1
|
||||
- uses: "./.github/actions/increase-max-open-files-on-macos"
|
||||
- uses: "./.github/actions/install-gflags-on-macos"
|
||||
- uses: "./.github/actions/pre-steps-macos"
|
||||
- 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 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 }}
|
||||
- name: Run shard 1 out of 4 test shards
|
||||
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 1,,4
|
||||
if: ${{ matrix.run_sharded_tests == 1 }}
|
||||
- name: Run shard 2 out of 4 test shards
|
||||
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 2,,4
|
||||
if: ${{ matrix.run_sharded_tests == 2 }}
|
||||
- name: Run shard 3 out of 4 test shards
|
||||
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j8 -I 3,,4
|
||||
if: ${{ matrix.run_sharded_tests == 3 }}
|
||||
run: cd build && make V=1 -j16
|
||||
- name: Run even tests
|
||||
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j16 -I 0,,2
|
||||
if: ${{ matrix.run_even_tests }}
|
||||
- name: Run odd tests
|
||||
run: ulimit -S -n `ulimit -H -n` && cd build && ctest -j16 -I 1,,2
|
||||
if: ${{ ! matrix.run_even_tests }}
|
||||
- uses: "./.github/actions/post-steps"
|
||||
# ======================== Windows with Tests ======================= #
|
||||
# NOTE: some windows jobs are in "nightly" to save resources
|
||||
build-windows-vs2022:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: windows-8-core
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: 1
|
||||
@@ -385,13 +404,11 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: evolvedbinary/rocksjava:centos7_x64-be
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
# The docker image is intentionally based on an OS that has an older GLIBC version.
|
||||
# That GLIBC is incompatibile with GitHub's actions/checkout. Thus we implement a manual checkout step.
|
||||
# NOTE: replaced evolvedbinary/rocksjava:centos7_x64-be with ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
# until a more appropriate docker image with C++20 support is made.
|
||||
- name: Checkout
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -408,21 +425,18 @@ jobs:
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- name: Test RocksDBJava
|
||||
# NOTE: replaced scl enable devtoolset-7 'make V=1 J=8 -j8 jtest'
|
||||
run: make V=1 J=8 -j8 jtest
|
||||
# post-steps skipped because of compatibility issues with docker image
|
||||
run: scl enable devtoolset-7 'make V=1 J=8 -j8 jtest'
|
||||
# NOTE: post-steps skipped because of compatibility issues with docker image
|
||||
build-linux-java-static:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
image: evolvedbinary/rocksjava:centos7_x64-be
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
# The docker image is intentionally based on an OS that has an older GLIBC version.
|
||||
# That GLIBC is incompatibile with GitHub's actions/checkout. Thus we implement a manual checkout step.
|
||||
# NOTE: replaced evolvedbinary/rocksjava:centos7_x64-be with ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
# until a more appropriate docker image with C++20 support is made.
|
||||
- name: Checkout
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -439,12 +453,11 @@ jobs:
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- name: Build RocksDBJava Static Library
|
||||
# NOTE: replaced scl enable devtoolset-7 'make V=1 J=8 -j8 rocksdbjavastatic'
|
||||
run: make V=1 J=8 -j8 rocksdbjavastatic
|
||||
# post-steps skipped because of compatibility issues with docker image
|
||||
run: scl enable devtoolset-7 'make V=1 J=8 -j8 rocksdbjavastatic'
|
||||
# NOTE: post-steps skipped because of compatibility issues with docker image
|
||||
build-macos-java:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-15-xlarge
|
||||
runs-on: macos-13
|
||||
env:
|
||||
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
|
||||
ROCKSDB_DISABLE_JEMALLOC: 1
|
||||
@@ -452,7 +465,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: 16.4.0
|
||||
xcode-version: 14.3.1
|
||||
- uses: "./.github/actions/increase-max-open-files-on-macos"
|
||||
- uses: "./.github/actions/install-gflags-on-macos"
|
||||
- uses: "./.github/actions/install-jdk8-on-macos"
|
||||
@@ -467,14 +480,14 @@ jobs:
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-macos-java-static:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-15-xlarge
|
||||
runs-on: macos-13
|
||||
env:
|
||||
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: 16.4.0
|
||||
xcode-version: 14.3.1
|
||||
- uses: "./.github/actions/increase-max-open-files-on-macos"
|
||||
- uses: "./.github/actions/install-gflags-on-macos"
|
||||
- uses: "./.github/actions/install-jdk8-on-macos"
|
||||
@@ -489,14 +502,14 @@ jobs:
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-macos-java-static-universal:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-15-xlarge
|
||||
runs-on: macos-13
|
||||
env:
|
||||
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: 16.4.0
|
||||
xcode-version: 14.3.1
|
||||
- uses: "./.github/actions/increase-max-open-files-on-macos"
|
||||
- uses: "./.github/actions/install-gflags-on-macos"
|
||||
- uses: "./.github/actions/install-jdk8-on-macos"
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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,14 +1,12 @@
|
||||
# This file @generated by:
|
||||
#$ python3 buckifier/buckify_rocksdb.py
|
||||
# --> DO NOT EDIT MANUALLY <--
|
||||
# This file is a Meta-specific integration for buck builds, so can
|
||||
# only be validated by Meta employees.
|
||||
# This file is a Facebook-specific integration for buck builds, so can
|
||||
# only be validated by Facebook 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",
|
||||
@@ -421,19 +419,17 @@ cpp_library_wrapper(name="rocksdb_tools_lib", srcs=[
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_cache_bench_tools_lib", srcs=["cache/cache_bench_tool.cc"], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_point_lock_bench_tools_lib", srcs=["utilities/transactions/lock/point/point_lock_bench_tool.cc"], deps=[":rocksdb_lib"], headers=[], link_whole=False, extra_test_libs=False)
|
||||
|
||||
rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
"db_stress_tool/batched_ops_stress.cc",
|
||||
"db_stress_tool/cf_consistency_stress.cc",
|
||||
"db_stress_tool/db_stress_common.cc",
|
||||
"db_stress_tool/db_stress_compaction_service.cc",
|
||||
"db_stress_tool/db_stress_compression_manager.cc",
|
||||
"db_stress_tool/db_stress_driver.cc",
|
||||
"db_stress_tool/db_stress_filters.cc",
|
||||
"db_stress_tool/db_stress_gflags.cc",
|
||||
"db_stress_tool/db_stress_listener.cc",
|
||||
"db_stress_tool/db_stress_shared_state.cc",
|
||||
"db_stress_tool/db_stress_stat.cc",
|
||||
"db_stress_tool/db_stress_test_base.cc",
|
||||
"db_stress_tool/db_stress_tool.cc",
|
||||
"db_stress_tool/db_stress_wide_merge_operator.cc",
|
||||
@@ -455,8 +451,6 @@ cpp_binary_wrapper(name="db_bench", srcs=["tools/db_bench.cc"], deps=[":rocksdb_
|
||||
|
||||
cpp_binary_wrapper(name="cache_bench", srcs=["cache/cache_bench.cc"], deps=[":rocksdb_cache_bench_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
|
||||
cpp_binary_wrapper(name="point_lock_bench", srcs=["utilities/transactions/lock/point/point_lock_bench.cc"], deps=[":rocksdb_point_lock_bench_tools_lib"], extra_preprocessor_flags=[], extra_bench_libs=False)
|
||||
|
||||
cpp_binary_wrapper(name="ribbon_bench", srcs=["microbench/ribbon_bench.cc"], deps=[], extra_preprocessor_flags=[], extra_bench_libs=True)
|
||||
|
||||
cpp_binary_wrapper(name="db_basic_bench", srcs=["microbench/db_basic_bench.cc"], deps=[], extra_preprocessor_flags=[], extra_bench_libs=True)
|
||||
@@ -4846,12 +4840,6 @@ 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"],
|
||||
@@ -5394,12 +5382,6 @@ cpp_unittest_wrapper(name="plain_table_db_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="point_lock_manager_stress_test",
|
||||
srcs=["utilities/transactions/lock/point/point_lock_manager_stress_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="point_lock_manager_test",
|
||||
srcs=["utilities/transactions/lock/point/point_lock_manager_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
+18
-64
@@ -27,7 +27,7 @@
|
||||
#
|
||||
# Linux:
|
||||
#
|
||||
# 1. Install a recent toolchain if you're on a older distro. C++20 required (GCC >= 11, Clang >= 10)
|
||||
# 1. Install a recent toolchain if you're on a older distro. C++17 required (GCC >= 7, Clang >= 5)
|
||||
# 2. mkdir build; cd build
|
||||
# 3. cmake ..
|
||||
# 4. make -j
|
||||
@@ -80,7 +80,6 @@ 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)
|
||||
@@ -101,7 +100,7 @@ endif()
|
||||
option(ROCKSDB_BUILD_SHARED "Build shared versions of the RocksDB libraries" ON)
|
||||
|
||||
if( NOT DEFINED CMAKE_CXX_STANDARD )
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
endif()
|
||||
|
||||
include(CMakeDependentOption)
|
||||
@@ -133,9 +132,7 @@ else()
|
||||
option(WITH_GFLAGS "build with GFlags" ON)
|
||||
endif()
|
||||
set(GFLAGS_LIB)
|
||||
# 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)
|
||||
if(WITH_GFLAGS)
|
||||
# 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)
|
||||
@@ -154,9 +151,6 @@ 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)
|
||||
@@ -209,20 +203,9 @@ if(WIN32 AND MSVC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(WIN_CI "Accelerate build speed and reduce build artifect size for github CI with MSVC" OFF)
|
||||
|
||||
if(MSVC)
|
||||
if(WIN_CI)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /nologo /EHsc /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /W4 /wd4127 /wd4996 /wd4100 /wd4324 /wd4702")
|
||||
else()
|
||||
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()
|
||||
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")
|
||||
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")
|
||||
@@ -330,7 +313,8 @@ if(NOT MSVC)
|
||||
endif()
|
||||
|
||||
# Check if -latomic is required or not
|
||||
if (NOT MSVC AND NOT APPLE)
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++17")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
std::atomic<uint64_t> x(0);
|
||||
@@ -467,33 +451,24 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Used to run optimized debug build and tests so we can run faster
|
||||
# Used to run CI build and tests so we can run faster
|
||||
option(OPTDBG "Build optimized debug build with MSVC" OFF)
|
||||
option(WITH_RUNTIME_DEBUG "build with debug version of runtime library" ON)
|
||||
if(MSVC)
|
||||
if (WIN_CI)
|
||||
if(OPTDBG)
|
||||
message(STATUS "Debug optimization is enabled")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG:FASTLINK")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG:FASTLINK")
|
||||
else()
|
||||
if(OPTDBG)
|
||||
message(STATUS "Debug optimization is enabled")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1")
|
||||
|
||||
# Minimal Build is deprecated after MSVC 2015
|
||||
if( MSVC_VERSION GREATER 1900 )
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm-")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od /RTC1")
|
||||
|
||||
# Minimal Build is deprecated after MSVC 2015
|
||||
if( MSVC_VERSION GREATER 1900 )
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm-")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm")
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm")
|
||||
endif()
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
|
||||
endif()
|
||||
|
||||
endif()
|
||||
if(WITH_RUNTIME_DEBUG)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}d")
|
||||
else()
|
||||
@@ -501,6 +476,8 @@ if(MSVC)
|
||||
endif()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /${RUNTIME_LIBRARY}")
|
||||
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
|
||||
endif()
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
@@ -652,12 +629,6 @@ 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)
|
||||
@@ -1098,21 +1069,12 @@ 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()
|
||||
@@ -1384,7 +1346,6 @@ 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
|
||||
@@ -1543,7 +1504,6 @@ if(WITH_TESTS)
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
utilities/transactions/transaction_test.cc
|
||||
utilities/transactions/lock/point/point_lock_manager_test.cc
|
||||
utilities/transactions/lock/point/point_lock_manager_stress_test.cc
|
||||
utilities/transactions/write_committed_transaction_ts_test.cc
|
||||
utilities/transactions/write_prepared_transaction_test.cc
|
||||
utilities/transactions/write_unprepared_transaction_test.cc
|
||||
@@ -1654,12 +1614,6 @@ if(WITH_BENCHMARK_TOOLS)
|
||||
utilities/persistent_cache/hash_table_bench.cc)
|
||||
target_link_libraries(hash_table_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
|
||||
add_executable(point_lock_bench${ARTIFACT_SUFFIX}
|
||||
utilities/transactions/lock/point/point_lock_bench.cc
|
||||
utilities/transactions/lock/point/point_lock_bench_tool.cc)
|
||||
target_link_libraries(point_lock_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
endif()
|
||||
|
||||
option(WITH_TRACE_TOOLS "build with trace tools" ON)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<CLToolExe>ccache_msvc_compiler.bat</CLToolExe>
|
||||
<CLToolPath>$(MSBuildThisFileDirectory)</CLToolPath>
|
||||
<UseMultiToolTask>true</UseMultiToolTask>
|
||||
<EnforceProcessCountAcrossBuilds>true</EnforceProcessCountAcrossBuilds>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
+10
-83
@@ -1,87 +1,14 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 10.10.1 (02/02/2026)
|
||||
## 10.6.2 (09/15/2025)
|
||||
### Bug Fixes
|
||||
* Fix Windows VS 2022 build errors.
|
||||
* Fix a race condition in FIFO size-based compaction where concurrent threads could select the same non-L0 file, causing assertion failures in debug builds or "Cannot delete table file from LSM tree" errors in release builds.
|
||||
|
||||
## 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)
|
||||
## 10.6.1 (09/05/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.
|
||||
* A new flag memtable_verify_per_key_checksum_on_seek is added to AdvancedColumnFamilyOptions. When it is enabled, it will validate key checksum along the binary search path on skiplist based memtable during seek operation.
|
||||
* Introduce option MultiScanArgs::use_async_io to enable asynchronous I/O during MultiScan, instead of waiting for I/O to be done in Prepare().
|
||||
* Add new option `MultiScanArgs::max_prefetch_size` that limits the memory usage of per file pinning of prefetched blocks.
|
||||
* Improved `sst_dump` by allowing standalone file and directory arguments without `--file=`. Also added new options and better output for `sst_dump --command=recompress`. See `sst_dump --help`
|
||||
|
||||
### Public API Changes
|
||||
* HyperClockCache with no `estimated_entry_charge` is now production-ready and is the preferred block cache implementation vs. LRUCache. Please consider updating your code to minimize the risk of hitting performance bottlenecks or anomalies from LRUCache. See cache.h for more detail.
|
||||
* RocksDB now requires a C++20 compatible compiler (GCC >= 11, Clang >= 10, Visual Studio >= 2019), including for any code using RocksDB headers.
|
||||
* MultiScanArgs used to have a default constructor with default parameter of BytewiseComparator. Now it always requires Comparator in its constructor.
|
||||
|
||||
### Behavior Changes
|
||||
* The default provided block cache implementation is now HyperClockCache instead of LRUCache, when `block_cache` is nullptr (default) and `no_block_cache==false` (default). We recommend explicitly creating a HyperClockCache block cache based on memory budget and sharing it across all column families and even DB instances. This change could expose previously hidden memory or resource leaks.
|
||||
|
||||
### Bug Fixes
|
||||
* Reported numbers for compaction and flush CPU usage now include time spent by parallel compression worker threads. This now means compaction/flush CPU usage could exceed the wall clock time.
|
||||
* Fix a race condition in FIFO size-based compaction where concurrent threads could select the same non-L0 file, causing assertion failures in debug builds or "Cannot delete table file from LSM tree" errors in release builds.
|
||||
* Fix a bug in RocksDB MultiScan with UDI when one of the scan ranges is determined to be empty by the UDI, which causes incorrect results.
|
||||
|
||||
### Performance Improvements
|
||||
* Add a new table property "rocksdb.key.smallest.seqno" which records the smallest sequence number of all keys in file. It makes ingesting DB generated files faster by
|
||||
avoiding scanning the whole file to find the smallest sequence number.
|
||||
* Add a new experimental PerKeyPointLockManager to improve efficiency under high lock contention. PointLockManager was not efficient when there is high write contention on same key, as it uses a single conditional variable per lock stripe. PerKeyPointLockManager uses per thread conditional variable supporting fifo order. Although this is an experimental feature. By default, it is disabled. A new boolean flag TransactionDBOptions::use_per_key_point_lock_mgr is added to optionally enable it. Search the flag in code for more info.
|
||||
Together, a new configuration TransactionOptions::deadlock_timeout_us is added, which allows the transaction to wait for a short period before perform deadlock detection. When the workload has low lock contention, the deadlock_timeout_us can be configured to be slightly higher than average transaction execution time, so that transaction would likely be able to take the lock before deadlock detection is performed when it is waiting for a lock. This allows transaction to reduce CPU cost on performing deadlock detection, which could be expensive in CPU time. When the workload has high lock contention, the deadlock_timeout_us can be configured to 0, so that transaction would perform deadlock detection immediately. By default the value is 0 to keep the behavior same as before.
|
||||
* Majorly improved CPU efficiency and scalability of parallel compression (`CompressionOptions::parallel_threads` > 1), though this efficiency improvement makes parallel compression currently incompatible with UserDefinedIndex and with old setting of `decouple_partitioned_filters=false`. Parallel compression is now considered a production-ready feature. Maximum performance is available with `-DROCKSDB_USE_STD_SEMAPHORES` at compile time, but this is not currently recommended because of reported bugs in implementations of `std::counting_semaphore`/`binary_semaphore`.
|
||||
|
||||
## 10.6.0 (08/22/2025)
|
||||
### New Features
|
||||
@@ -116,7 +43,7 @@ Together, a new configuration TransactionOptions::deadlock_timeout_us is added,
|
||||
* DB option skip_checking_sst_file_sizes_on_db_open is deprecated, in favor of validating file size in parallel in a thread pool, when db is opened. When DB is opened, with paranoid check enabled, a file with the wrong size would fail the DB open. With paranoid check disabled, the DB open would succeed, the column family with the corrupted file would not be read or write, while the other healthy column families could be read and write normally. When max_open_files option is not set to -1, only a subset of the files will be opened and checked. The rest of the files will be opened and checked when they are accessed.
|
||||
|
||||
### Behavior Changes
|
||||
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occurred.
|
||||
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occured.
|
||||
* A new API GetFileSize is added to FSRandomAccessFile interface class. It uses fstat vs stat on the posix implementation which is more efficient. Caller could use it to get file size faster. This function might be required in the future for FileSystem implementation outside of the RocksDB code base.
|
||||
* RocksDB now triggers eligible compactions every 12 hours when periodic compaction is configured. This solves a limitation of the compaction trigger mechanism, which would only trigger compaction after specific events like flush, compaction, or SetOptions.
|
||||
|
||||
@@ -173,7 +100,7 @@ system's prefetch) on SST file during compaction read
|
||||
* Deprecated API `DB::MaxMemCompactionLevel()`.
|
||||
* Deprecated `ReadOptions::ignore_range_deletions`.
|
||||
* Deprecated API `experimental::PromoteL0()`.
|
||||
* Added arbitrary string map for additional options to be overridden for remote compactions
|
||||
* Added arbitrary string map for additional options to be overriden for remote compactions
|
||||
* The fail_if_options_file_error option in DBOptions has been removed. The behavior now is to always return failure in any API that fails to persist the OPTIONS file.
|
||||
|
||||
### Behavior Changes
|
||||
@@ -323,7 +250,7 @@ system's prefetch) on SST file during compaction read
|
||||
* In FIFO compaction, compactions for changing file temperature (configured by option `file_temperature_age_thresholds`) will compact one file at a time, instead of merging multiple eligible file together (#13018).
|
||||
* Support ingesting db generated files using hard link, i.e. IngestExternalFileOptions::move_files/link_files and IngestExternalFileOptions::allow_db_generated_files.
|
||||
* Add a new file ingestion option `IngestExternalFileOptions::link_files` to hard link input files and preserve original files links after ingestion.
|
||||
* DB::Close now untracks files in SstFileManager, making available any space used
|
||||
* DB::Close now untracks files in SstFileManager, making avaialble any space used
|
||||
by them. Prior to this change they would be orphaned until the DB is re-opened.
|
||||
|
||||
### Bug Fixes
|
||||
@@ -519,7 +446,7 @@ MultiGetBenchmarks.multiGetList10 no_column_family 10000 16 100 1024 thrpt 25 76
|
||||
* Removed deprecated option `ColumnFamilyOptions::check_flush_compaction_key_order`
|
||||
* Remove the default `WritableFile::GetFileSize` and `FSWritableFile::GetFileSize` implementation that returns 0 and make it pure virtual, so that subclasses are enforced to explicitly provide an implementation.
|
||||
* Removed deprecated option `ColumnFamilyOptions::level_compaction_dynamic_file_size`
|
||||
* Removed tickers with typos "rocksdb.error.handler.bg.error.count", "rocksdb.error.handler.bg.io.error.count", "rocksdb.error.handler.bg.retryable.io.error.count".
|
||||
* Removed tickers with typos "rocksdb.error.handler.bg.errro.count", "rocksdb.error.handler.bg.io.errro.count", "rocksdb.error.handler.bg.retryable.io.errro.count".
|
||||
* Remove the force mode for `EnableFileDeletions` API because it is unsafe with no known legitimate use.
|
||||
* Removed deprecated option `ColumnFamilyOptions::ignore_max_compaction_bytes_for_input`
|
||||
* `sst_dump --command=check` now compares the number of records in a table with `num_entries` in table property, and reports corruption if there is a mismatch. API `SstFileDumper::ReadSequential()` is updated to optionally do this verification. (#12322)
|
||||
@@ -546,7 +473,7 @@ MultiGetBenchmarks.multiGetList10 no_column_family 10000 16 100 1024 thrpt 25 76
|
||||
* Exposed options ttl via c api.
|
||||
|
||||
### Behavior Changes
|
||||
* `rocksdb.blobdb.blob.file.write.micros` expands to also measure time writing the header and footer. Therefore the COUNT may be higher and values may be smaller than before. For stacked BlobDB, it no longer measures the time of explicitly flushing blob file.
|
||||
* `rocksdb.blobdb.blob.file.write.micros` expands to also measure time writing the header and footer. Therefore the COUNT may be higher and values may be smaller than before. For stacked BlobDB, it no longer measures the time of explictly flushing blob file.
|
||||
* Files will be compacted to the next level if the data age exceeds periodic_compaction_seconds except for the last level.
|
||||
* Reduced the compaction debt ratio trigger for scheduling parallel compactions
|
||||
* For leveled compaction with default compaction pri (kMinOverlappingRatio), files marked for compaction will be prioritized over files not marked when picking a file from a level for compaction.
|
||||
@@ -611,7 +538,7 @@ want to continue to use force enabling, they need to explicitly pass a `true` to
|
||||
|
||||
### Behavior Changes
|
||||
* During off-peak hours defined by `daily_offpeak_time_utc`, the compaction picker will select a larger number of files for periodic compaction. This selection will include files that are projected to expire by the next off-peak start time, ensuring that these files are not chosen for periodic compaction outside of off-peak hours.
|
||||
* If an error occurs when writing to a trace file after `DB::StartTrace()`, the subsequent trace writes are skipped to avoid writing to a file that has previously seen error. In this case, `DB::EndTrace()` will also return a non-ok status with info about the error occurred previously in its status message.
|
||||
* If an error occurs when writing to a trace file after `DB::StartTrace()`, the subsequent trace writes are skipped to avoid writing to a file that has previously seen error. In this case, `DB::EndTrace()` will also return a non-ok status with info about the error occured previously in its status message.
|
||||
* Deleting stale files upon recovery are delegated to SstFileManger if available so they can be rate limited.
|
||||
* Make RocksDB only call `TablePropertiesCollector::Finish()` once.
|
||||
* When `WAL_ttl_seconds > 0`, we now process archived WALs for deletion at least every `WAL_ttl_seconds / 2` seconds. Previously it could be less frequent in case of small `WAL_ttl_seconds` values when size-based expiration (`WAL_size_limit_MB > 0 `) was simultaneously enabled.
|
||||
@@ -1399,7 +1326,7 @@ Note: The next release will be major release 7.0. See https://github.com/faceboo
|
||||
### Public API change
|
||||
* Extend WriteBatch::AssignTimestamp and AssignTimestamps API so that both functions can accept an optional `checker` argument that performs additional checking on timestamp sizes.
|
||||
* Introduce a new EventListener callback that will be called upon the end of automatic error recovery.
|
||||
* Add IncreaseFullHistoryTsLow API so users can advance each column family's full_history_ts_low separately.
|
||||
* Add IncreaseFullHistoryTsLow API so users can advance each column family's full_history_ts_low seperately.
|
||||
* Add GetFullHistoryTsLow API so users can query current full_history_low value of specified column family.
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
+5
-5
@@ -6,7 +6,7 @@ than release mode.
|
||||
|
||||
RocksDB's library should be able to compile without any dependency installed,
|
||||
although we recommend installing some compression libraries (see below).
|
||||
We do depend on newer gcc/clang with C++20 support (GCC >= 11, Clang >= 10).
|
||||
We do depend on newer gcc/clang with C++17 support (GCC >= 7, Clang >= 5).
|
||||
|
||||
There are few options when compiling RocksDB:
|
||||
|
||||
@@ -60,7 +60,7 @@ most processors made since roughly 2013.
|
||||
## Supported platforms
|
||||
|
||||
* **Linux - Ubuntu**
|
||||
* Upgrade your gcc to version at least 11 to get C++20 support.
|
||||
* Upgrade your gcc to version at least 7 to get C++17 support.
|
||||
* Install gflags. First, try: `sudo apt-get install libgflags-dev`
|
||||
If this doesn't work and you're using Ubuntu, here's a nice tutorial:
|
||||
(http://askubuntu.com/questions/312173/installing-gflags-12-04)
|
||||
@@ -72,7 +72,7 @@ most processors made since roughly 2013.
|
||||
* Install zstandard: `sudo apt-get install libzstd-dev`.
|
||||
|
||||
* **Linux - CentOS / RHEL**
|
||||
* Upgrade your gcc to version at least 11 to get C++20 support
|
||||
* Upgrade your gcc to version at least 7 to get C++17 support
|
||||
* Install gflags:
|
||||
|
||||
git clone https://github.com/gflags/gflags.git
|
||||
@@ -122,7 +122,7 @@ most processors made since roughly 2013.
|
||||
make && sudo make install
|
||||
|
||||
* **OS X**:
|
||||
* Install latest C++ compiler that supports C++20:
|
||||
* Install latest C++ compiler that supports C++ 17:
|
||||
* Update XCode: run `xcode-select --install` (or install it from XCode App's settting).
|
||||
* Install via [homebrew](http://brew.sh/).
|
||||
* If you're first time developer in MacOS, you still need to run: `xcode-select --install` in your command line.
|
||||
@@ -213,7 +213,7 @@ most processors made since roughly 2013.
|
||||
export PATH=/opt/freeware/bin:$PATH
|
||||
|
||||
* **Solaris Sparc**
|
||||
* Install GCC 11 and higher.
|
||||
* Install GCC 7 and higher.
|
||||
* Use these environment variables:
|
||||
|
||||
export CC=gcc
|
||||
|
||||
@@ -148,8 +148,10 @@ ifeq ($(USE_COROUTINES), 1)
|
||||
USE_FOLLY = 1
|
||||
# glog/logging.h requires HAVE_CXX11_ATOMIC
|
||||
OPT += -DUSE_COROUTINES -DHAVE_CXX11_ATOMIC
|
||||
ROCKSDB_CXX_STANDARD = c++2a
|
||||
USE_RTTI = 1
|
||||
ifneq ($(USE_CLANG), 1)
|
||||
ROCKSDB_CXX_STANDARD = c++20
|
||||
PLATFORM_CXXFLAGS += -fcoroutines
|
||||
endif
|
||||
endif
|
||||
@@ -296,28 +298,6 @@ $(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; \
|
||||
@@ -468,7 +448,83 @@ else
|
||||
PLATFORM_CXXFLAGS += -isystem $(GTEST_DIR)
|
||||
endif
|
||||
|
||||
include folly.mk
|
||||
# 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
|
||||
|
||||
ifdef TEST_CACHE_LINE_SIZE
|
||||
PLATFORM_CCFLAGS += -DTEST_CACHE_LINE_SIZE=$(TEST_CACHE_LINE_SIZE)
|
||||
@@ -582,14 +638,13 @@ endif
|
||||
TEST_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES)) $(GTEST)
|
||||
BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(BENCH_LIB_SOURCES))
|
||||
CACHE_BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(CACHE_BENCH_LIB_SOURCES))
|
||||
POINT_LOCK_BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(POINT_LOCK_BENCH_LIB_SOURCES))
|
||||
TOOL_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TOOL_LIB_SOURCES))
|
||||
ANALYZE_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(ANALYZER_LIB_SOURCES))
|
||||
STRESS_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(STRESS_LIB_SOURCES))
|
||||
|
||||
# Exclude build_version.cc -- a generated source file -- from all sources. Not needed for dependencies
|
||||
ALL_SOURCES = $(filter-out util/build_version.cc, $(LIB_SOURCES)) $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES) $(GTEST_DIR)/gtest/gtest-all.cc
|
||||
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(CACHE_BENCH_LIB_SOURCES) $(POINT_LOCK_BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(CACHE_BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
ALL_SOURCES += $(TEST_MAIN_SOURCES) $(TOOL_MAIN_SOURCES) $(BENCH_MAIN_SOURCES)
|
||||
ALL_SOURCES += $(ROCKSDB_PLUGIN_SOURCES) $(ROCKSDB_PLUGIN_TESTS)
|
||||
|
||||
@@ -628,8 +683,7 @@ am__v_CCH_1 =
|
||||
# user build settings
|
||||
%.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 $<
|
||||
$(CXX) -I. -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
|
||||
|
||||
check-headers: $(HEADER_OK_FILES)
|
||||
|
||||
@@ -1291,9 +1345,6 @@ block_cache_trace_analyzer: $(OBJ_DIR)/tools/block_cache_analyzer/block_cache_tr
|
||||
cache_bench: $(OBJ_DIR)/cache/cache_bench.o $(CACHE_BENCH_OBJECTS) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
point_lock_bench: $(OBJ_DIR)/utilities/transactions/lock/point/point_lock_bench.o $(POINT_LOCK_BENCH_OBJECTS) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
persistent_cache_bench: $(OBJ_DIR)/utilities/persistent_cache/persistent_cache_bench.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1374,13 +1425,13 @@ agg_merge_test: $(OBJ_DIR)/utilities/agg_merge/agg_merge_test.o $(TEST_LIBRARY)
|
||||
stringappend_test: $(OBJ_DIR)/utilities/merge_operators/string_append/stringappend_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cassandra_format_test: $(OBJ_DIR)/utilities/cassandra/cassandra_format_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
cassandra_format_test: $(OBJ_DIR)/utilities/cassandra/cassandra_format_test.o $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cassandra_functional_test: $(OBJ_DIR)/utilities/cassandra/cassandra_functional_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
cassandra_functional_test: $(OBJ_DIR)/utilities/cassandra/cassandra_functional_test.o $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cassandra_row_merge_test: $(OBJ_DIR)/utilities/cassandra/cassandra_row_merge_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
cassandra_row_merge_test: $(OBJ_DIR)/utilities/cassandra/cassandra_row_merge_test.o $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cassandra_serialize_test: $(OBJ_DIR)/utilities/cassandra/cassandra_serialize_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
@@ -1443,9 +1494,6 @@ 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)
|
||||
|
||||
@@ -1833,9 +1881,6 @@ heap_test: $(OBJ_DIR)/util/heap_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
point_lock_manager_test: utilities/transactions/lock/point/point_lock_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
point_lock_manager_stress_test: utilities/transactions/lock/point/point_lock_manager_stress_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
transaction_test: $(OBJ_DIR)/utilities/transactions/transaction_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -2206,7 +2251,7 @@ libsnappy.a: snappy-$(SNAPPY_VER).tar.gz
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
tar xvzf snappy-$(SNAPPY_VER).tar.gz
|
||||
mkdir snappy-$(SNAPPY_VER)/build
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' CXXFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CXXFLAGS} ${EXTRA_CXXFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DSNAPPY_BUILD_BENCHMARKS=OFF -DSNAPPY_BUILD_TESTS=OFF ${PLATFORM_CMAKE_FLAGS} .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' CXXFLAGS='$(ARCHFLAG) ${JAVA_STATIC_DEPS_CXXFLAGS} ${EXTRA_CXXFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DSNAPPY_BUILD_BENCHMARKS=OFF -DSNAPPY_BUILD_TESTS=OFF --compile-no-warning-as-error ${PLATFORM_CMAKE_FLAGS} .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
|
||||
|
||||
lz4-$(LZ4_VER).tar.gz:
|
||||
@@ -2442,6 +2487,40 @@ 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;
|
||||
|
||||
# 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 e95383b7c8b5b1e46cf47acf2f317d54f93c8268
|
||||
@# 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,9 +135,6 @@ 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",
|
||||
@@ -209,12 +206,6 @@ def generate_buck(repo_path, deps_map):
|
||||
src_mk.get("CACHE_BENCH_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"],
|
||||
)
|
||||
# rocksdb_point_lock_bench_tools_lib
|
||||
BUCK.add_library(
|
||||
"rocksdb_point_lock_bench_tools_lib",
|
||||
src_mk.get("POINT_LOCK_BENCH_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"],
|
||||
)
|
||||
# rocksdb_stress_lib
|
||||
BUCK.add_rocksdb_library(
|
||||
"rocksdb_stress_lib",
|
||||
@@ -238,12 +229,6 @@ def generate_buck(repo_path, deps_map):
|
||||
BUCK.add_binary(
|
||||
"cache_bench", ["cache/cache_bench.cc"], [":rocksdb_cache_bench_tools_lib"]
|
||||
)
|
||||
# point_lock_bench binary
|
||||
BUCK.add_binary(
|
||||
"point_lock_bench",
|
||||
["utilities/transactions/lock/point/point_lock_bench.cc"],
|
||||
[":rocksdb_point_lock_bench_tools_lib"]
|
||||
)
|
||||
# bench binaries
|
||||
for src in src_mk.get("MICROBENCH_SOURCES", []):
|
||||
name = src.rsplit("/", 1)[1].split(".")[0] if "/" in src else src.split(".")[0]
|
||||
|
||||
@@ -45,11 +45,6 @@ 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,12 +1,10 @@
|
||||
# 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).
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
rocksdb_target_header_template = """# This file \100generated by:
|
||||
#$ python3 buckifier/buckify_rocksdb.py{extra_argv}
|
||||
# --> DO NOT EDIT MANUALLY <--
|
||||
# This file is a Meta-specific integration for buck builds, so can
|
||||
# only be validated by Meta employees.
|
||||
# This file is a Facebook-specific integration for buck builds, so can
|
||||
# only be validated by Facebook 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")
|
||||
|
||||
@@ -43,8 +41,3 @@ 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}")
|
||||
"""
|
||||
@@ -45,11 +45,11 @@ if test -z "$OUTPUT"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# we depend on C++20, but should be compatible with newer standards
|
||||
# we depend on C++17, but should be compatible with newer standards
|
||||
if [ "$ROCKSDB_CXX_STANDARD" ]; then
|
||||
PLATFORM_CXXFLAGS="-std=$ROCKSDB_CXX_STANDARD"
|
||||
else
|
||||
PLATFORM_CXXFLAGS="-std=c++20"
|
||||
PLATFORM_CXXFLAGS="-std=c++17"
|
||||
fi
|
||||
|
||||
# we currently depend on POSIX platform
|
||||
@@ -311,8 +311,7 @@ EOF
|
||||
EOF
|
||||
then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
|
||||
# Hack: don't link extra gflags assuming it comes with folly
|
||||
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
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>
|
||||
@@ -321,8 +320,7 @@ EOF
|
||||
EOF
|
||||
then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags"
|
||||
# Hack: don't link extra gflags assuming it comes with folly
|
||||
[ "$USE_FOLLY" ] || PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
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>
|
||||
@@ -774,7 +772,6 @@ 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
|
||||
|
||||
@@ -819,7 +816,6 @@ 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
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved.
|
||||
#
|
||||
# Check for some simple mistakes in public headers (on the command line)
|
||||
# that should prevent commit or push
|
||||
|
||||
BAD=""
|
||||
|
||||
# Look for potential for ODR violations caused by public headers depending on
|
||||
# build parameters that could vary between RocksDB build and application build.
|
||||
# * Cases like LUA, ROCKSDB_NAMESPACE, and ROCKSDB_ASSERT_STATUS_CHECKED are
|
||||
# intentional, hard to avoid. (We expect definitions to change and the user
|
||||
# should also.)
|
||||
# * Cases like _WIN32, OS_WIN, and __cplusplus are essentially ODR-safe.
|
||||
# * Cases like
|
||||
# #ifdef BLAH // ODR-SAFE
|
||||
# #undef BLAH
|
||||
# #endif
|
||||
# that should not cause ODR violations can be exempted with the ODR-SAFE
|
||||
# marker recognized here.
|
||||
|
||||
grep -nHE '^#if' -- "$@" | grep -vE 'ROCKSDB_NAMESPACE|ROCKSDB_ASSERT_STATUS_CHECKED|LUA|_WIN32|OS_WIN|ODR-SAFE|__cplusplus|ROCKSDB_DLL|ROCKSDB_LIBRARY_EXPORTS'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ #if in public API could cause an ODR violation."
|
||||
echo " Add // ODR-SAFE if verified safe."
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
if [ "$BAD" ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -148,70 +148,6 @@ 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!"
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# INSTRUCTIONS:
|
||||
# I was not able to build docker images on an isolated devserver because of
|
||||
# issues with proxy internet access. Use a public cloud or other Linux system.
|
||||
# (I used a Debian system after installing docker features, adding my user to
|
||||
# the docker and docker-registry groups, and logging out and back in to pick
|
||||
# those up.)
|
||||
#
|
||||
# Follow https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic
|
||||
# to login with your GitHub credentials, as in
|
||||
#
|
||||
# $ docker login ghcr.io -u pdillinger
|
||||
#
|
||||
# and paste the limited-purpose GitHub token into the terminal.
|
||||
#
|
||||
# Then in the build_tools/ubuntu22_image directory, (bump minor version for
|
||||
# random docker file updates, major version tracks Ubuntu release)
|
||||
#
|
||||
# $ docker build -t ghcr.io/facebook/rocksdb_ubuntu:22.0
|
||||
# $ docker push ghcr.io/facebook/rocksdb_ubuntu:22.0
|
||||
#
|
||||
# Might need to change visibility to public through
|
||||
# https://github.com/orgs/facebook/packages/container/rocksdb_ubuntu/settings
|
||||
# or similar.
|
||||
|
||||
# from official ubuntu 22.04
|
||||
FROM ubuntu:22.04
|
||||
# update system
|
||||
RUN apt-get update
|
||||
RUN apt-get upgrade -y
|
||||
# install basic tools
|
||||
RUN apt-get install -y vim wget curl
|
||||
# install tzdata noninteractive
|
||||
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
|
||||
# install git and default compilers
|
||||
RUN apt-get install -y git gcc g++ clang clang-tools
|
||||
# install basic package
|
||||
RUN apt-get install -y lsb-release software-properties-common gnupg
|
||||
# install gflags, tbb
|
||||
RUN apt-get install -y libgflags-dev libtbb-dev
|
||||
# install compression libs
|
||||
RUN apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
|
||||
# install cmake
|
||||
RUN apt-get install -y cmake
|
||||
RUN apt-get install -y libssl-dev
|
||||
# install clang-13
|
||||
WORKDIR /root
|
||||
RUN wget https://apt.llvm.org/llvm.sh
|
||||
RUN chmod +x llvm.sh
|
||||
RUN ./llvm.sh 13 all
|
||||
# There are incompatibilities between clang with -std=c++20 and libstdc++
|
||||
# provided by gcc, so we have to compile with clang-13 using -stdlib=libc++
|
||||
# and only one version of libc++ can be installed on the system at one time.
|
||||
# So to avoid confusion we remove unusable clang-14 also.
|
||||
RUN apt-get install libc++-13-dev libc++abi-13-dev
|
||||
RUN apt-get purge -y clang-14 && apt-get autoremove -y
|
||||
|
||||
# install gcc-10 and more, default is 11
|
||||
RUN apt-get install -y gcc-10 g++-10
|
||||
RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
RUN apt-get install -y gcc-13 g++-13
|
||||
# install apt-get install -y valgrind
|
||||
RUN apt-get install -y valgrind
|
||||
# install folly depencencies
|
||||
# Missing compatible libunwind: RUN apt-get install -y libgoogle-glog-dev
|
||||
# So instead install from source. This currently requires compiling with
|
||||
# -DGLOG_USE_GLOG_EXPORT
|
||||
RUN wget https://github.com/google/glog/archive/refs/tags/v0.7.1.tar.gz && tar xzf v0.7.1.tar.gz && cd glog-0.7.1/ && cmake -S . -B build -G "Unix Makefiles" && cmake --build build && cmake --build build --target install && cd .. && rm -rf v0.7.1.tar.gz glog-0.7.1
|
||||
# install openjdk 8
|
||||
RUN apt-get install -y openjdk-8-jdk
|
||||
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
# install mingw
|
||||
RUN apt-get install -y mingw-w64
|
||||
|
||||
# install gtest-parallel package
|
||||
RUN git clone --single-branch --branch master --depth 1 https://github.com/google/gtest-parallel.git ~/gtest-parallel
|
||||
ENV PATH $PATH:/root/gtest-parallel
|
||||
|
||||
# install libprotobuf for fuzzers test
|
||||
RUN apt-get install -y ninja-build binutils liblzma-dev libz-dev pkg-config autoconf libtool
|
||||
RUN git clone --branch v1.0 https://github.com/google/libprotobuf-mutator.git ~/libprotobuf-mutator && cd ~/libprotobuf-mutator && git checkout ffd86a32874e5c08a143019aad1aaf0907294c9f && mkdir build && cd build && cmake .. -GNinja -DCMAKE_C_COMPILER=clang-13 -DCMAKE_CXX_COMPILER=clang++-13 -DCMAKE_BUILD_TYPE=Release -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON && ninja && ninja install
|
||||
ENV PKG_CONFIG_PATH /usr/local/OFF/:/root/libprotobuf-mutator/build/external.protobuf/lib/pkgconfig/
|
||||
ENV PROTOC_BIN /root/libprotobuf-mutator/build/external.protobuf/bin/protoc
|
||||
|
||||
# install the latest google benchmark
|
||||
RUN git clone --depth 1 --branch v1.7.0 https://github.com/google/benchmark.git ~/benchmark && cd ~/benchmark && mkdir build && cd build && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0 && ninja && ninja install && cd ~ && rm -rf /root/benchmark
|
||||
|
||||
# clean up
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
@@ -1,72 +0,0 @@
|
||||
# INSTRUCTIONS:
|
||||
# I was not able to build docker images on an isolated devserver because of
|
||||
# issues with proxy internet access. Use a public cloud or other Linux system.
|
||||
# (I used a Debian system after installing docker features, adding my user to
|
||||
# the docker and docker-registry groups, and logging out and back in to pick
|
||||
# those up.)
|
||||
#
|
||||
# Follow https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-with-a-personal-access-token-classic
|
||||
# to login with your GitHub credentials, as in
|
||||
#
|
||||
# $ docker login ghcr.io -u pdillinger
|
||||
#
|
||||
# and paste the limited-purpose GitHub token into the terminal.
|
||||
#
|
||||
# Then in the build_tools/ubuntu24_image directory, (bump minor version for
|
||||
# random docker file updates, major version tracks Ubuntu release)
|
||||
#
|
||||
# $ docker build -t ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
# $ docker push ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
#
|
||||
# Might need to change visibility to public through
|
||||
# https://github.com/orgs/facebook/packages/container/rocksdb_ubuntu/settings
|
||||
# or similar.
|
||||
|
||||
# from official ubuntu 24.04
|
||||
FROM ubuntu:24.04
|
||||
# update system
|
||||
RUN apt-get update
|
||||
RUN apt-get upgrade -y
|
||||
# install basic tools
|
||||
RUN apt-get install -y vim wget curl
|
||||
# install tzdata noninteractive
|
||||
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
|
||||
# install git and default compilers
|
||||
RUN apt-get install -y git gcc g++ clang clang-tools
|
||||
# install basic package
|
||||
RUN apt-get install -y lsb-release software-properties-common gnupg
|
||||
# install gflags, tbb
|
||||
RUN apt-get install -y libgflags-dev libtbb-dev
|
||||
# install compression libs
|
||||
RUN apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
|
||||
# install cmake
|
||||
RUN apt-get install -y cmake
|
||||
RUN apt-get install -y libssl-dev
|
||||
|
||||
# install gcc-12 and more, default is 13
|
||||
RUN apt-get install -y gcc-12 g++-12 gcc-14 g++-14
|
||||
# install apt-get install -y valgrind
|
||||
RUN apt-get install -y valgrind
|
||||
# install folly depencencies
|
||||
RUN apt-get install -y libgoogle-glog-dev
|
||||
# install openjdk 8
|
||||
RUN apt-get install -y openjdk-8-jdk
|
||||
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
# install mingw
|
||||
RUN apt-get install -y mingw-w64
|
||||
|
||||
# install gtest-parallel package
|
||||
RUN git clone --single-branch --branch master --depth 1 https://github.com/google/gtest-parallel.git ~/gtest-parallel
|
||||
ENV PATH $PATH:/root/gtest-parallel
|
||||
|
||||
# install libprotobuf for fuzzers test
|
||||
RUN apt-get install -y ninja-build binutils liblzma-dev libz-dev pkg-config autoconf libtool
|
||||
RUN git clone --branch v1.0 https://github.com/google/libprotobuf-mutator.git ~/libprotobuf-mutator && cd ~/libprotobuf-mutator && git checkout ffd86a32874e5c08a143019aad1aaf0907294c9f && mkdir build && cd build && cmake .. -GNinja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON && ninja && ninja install
|
||||
ENV PKG_CONFIG_PATH /usr/local/OFF/:/root/libprotobuf-mutator/build/external.protobuf/lib/pkgconfig/
|
||||
ENV PROTOC_BIN /root/libprotobuf-mutator/build/external.protobuf/bin/protoc
|
||||
|
||||
# install the latest google benchmark
|
||||
RUN git clone --depth 1 --branch v1.7.0 https://github.com/google/benchmark.git ~/benchmark && cd ~/benchmark && mkdir build && cd build && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0 && ninja && ninja install && cd ~ && rm -rf /root/benchmark
|
||||
|
||||
# clean up
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
Vendored
+9
-75
@@ -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, "hyper_clock_cache", "Type of block cache.");
|
||||
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
|
||||
|
||||
DEFINE_bool(use_jemalloc_no_dump_allocator, false,
|
||||
"Whether to use JemallocNoDumpAllocator");
|
||||
@@ -184,11 +184,6 @@ DEFINE_bool(sck_randomize, false,
|
||||
DEFINE_bool(sck_footer_unique_id, false,
|
||||
"(-stress_cache_key) Simulate using proposed footer unique id");
|
||||
// ## END stress_cache_key sub-tool options ##
|
||||
// ## BEGIN stress_cache_instances sub-tool options ##
|
||||
DEFINE_uint32(stress_cache_instances, 0,
|
||||
"If > 0, run cache instance stress test instead");
|
||||
// Uses cache_size and cache_type, maybe more
|
||||
// ## END stress_cache_instance sub-tool options ##
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -392,12 +387,7 @@ class CacheBench {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
cache_ = MakeCache();
|
||||
}
|
||||
|
||||
~CacheBench() = default;
|
||||
|
||||
static std::shared_ptr<Cache> MakeCache() {
|
||||
std::shared_ptr<MemoryAllocator> allocator;
|
||||
if (FLAGS_use_jemalloc_no_dump_allocator) {
|
||||
JemallocAllocatorOptions opts;
|
||||
@@ -416,12 +406,12 @@ class CacheBench {
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
opts.memory_allocator = allocator;
|
||||
opts.eviction_effort_cap = FLAGS_eviction_effort_cap;
|
||||
if (FLAGS_cache_type == "fixed_hyper_clock_cache") {
|
||||
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
opts.estimated_entry_charge = FLAGS_value_bytes_estimate > 0
|
||||
? FLAGS_value_bytes_estimate
|
||||
: FLAGS_value_bytes;
|
||||
} else if (FLAGS_cache_type == "auto_hyper_clock_cache" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
} else if (FLAGS_cache_type == "auto_hyper_clock_cache") {
|
||||
if (FLAGS_value_bytes_estimate > 0) {
|
||||
opts.min_avg_entry_charge = FLAGS_value_bytes_estimate;
|
||||
}
|
||||
@@ -430,7 +420,7 @@ class CacheBench {
|
||||
exit(1);
|
||||
}
|
||||
ConfigureSecondaryCache(opts);
|
||||
return opts.MakeSharedCache();
|
||||
cache_ = opts.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "lru_cache") {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
|
||||
false /* strict_capacity_limit */,
|
||||
@@ -438,13 +428,15 @@ class CacheBench {
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
opts.memory_allocator = allocator;
|
||||
ConfigureSecondaryCache(opts);
|
||||
return NewLRUCache(opts);
|
||||
cache_ = NewLRUCache(opts);
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
~CacheBench() = default;
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(FLAGS_seed);
|
||||
KeyGen keygen;
|
||||
@@ -498,7 +490,7 @@ class CacheBench {
|
||||
|
||||
PrintEnv();
|
||||
SharedState shared(this);
|
||||
std::vector<std::unique_ptr<ThreadState>> threads(FLAGS_threads);
|
||||
std::vector<std::unique_ptr<ThreadState> > threads(FLAGS_threads);
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
threads[i].reset(new ThreadState(i, &shared));
|
||||
std::thread(ThreadBody, threads[i].get()).detach();
|
||||
@@ -1160,59 +1152,6 @@ class StressCacheKey {
|
||||
double multiplier_ = 0.0;
|
||||
};
|
||||
|
||||
// cache_bench -stress_cache_instances is a partially independent embedded tool
|
||||
// for evaluating the time and space required to create and destroy many cache
|
||||
// instances, as this is considered important for a default cache implementation
|
||||
// which could see many throw-away instances in handling of Options, or created
|
||||
// in large numbers for many very small DBs with many CFs. Prefix command line
|
||||
// with /usr/bin/time to see max RSS memory.
|
||||
class StressCacheInstances {
|
||||
public:
|
||||
void Run() {
|
||||
const int kNumIterations = 10;
|
||||
const auto clock = SystemClock::Default().get();
|
||||
caches_.reserve(FLAGS_stress_cache_instances);
|
||||
|
||||
uint64_t total_create_time_us = 0;
|
||||
uint64_t total_destroy_time_us = 0;
|
||||
|
||||
for (int iter = 0; iter < kNumIterations; ++iter) {
|
||||
// Create many cache instances
|
||||
uint64_t start_create = clock->NowMicros();
|
||||
for (uint32_t i = 0; i < FLAGS_stress_cache_instances; ++i) {
|
||||
caches_.emplace_back(CacheBench::MakeCache());
|
||||
}
|
||||
uint64_t end_create = clock->NowMicros();
|
||||
uint64_t create_time = end_create - start_create;
|
||||
total_create_time_us += create_time;
|
||||
|
||||
// Destroy them
|
||||
uint64_t start_destroy = clock->NowMicros();
|
||||
caches_.clear();
|
||||
uint64_t end_destroy = clock->NowMicros();
|
||||
uint64_t destroy_time = end_destroy - start_destroy;
|
||||
total_destroy_time_us += destroy_time;
|
||||
|
||||
printf(
|
||||
"Iteration %d: Created %u caches in %.3f ms, destroyed in %.3f ms\n",
|
||||
iter + 1, FLAGS_stress_cache_instances, create_time / 1000.0,
|
||||
destroy_time / 1000.0);
|
||||
}
|
||||
|
||||
printf("Average creation time: %.3f ms (%.1f us per cache)\n",
|
||||
static_cast<double>(total_create_time_us) / kNumIterations / 1000.0,
|
||||
static_cast<double>(total_create_time_us) / kNumIterations /
|
||||
FLAGS_stress_cache_instances);
|
||||
printf("Average destruction time: %.3f ms (%.1f us per cache)\n",
|
||||
static_cast<double>(total_destroy_time_us) / kNumIterations / 1000.0,
|
||||
static_cast<double>(total_destroy_time_us) / kNumIterations /
|
||||
FLAGS_stress_cache_instances);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<Cache>> caches_;
|
||||
};
|
||||
|
||||
int cache_bench_tool(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
@@ -1223,11 +1162,6 @@ int cache_bench_tool(int argc, char** argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (FLAGS_stress_cache_instances > 0) {
|
||||
StressCacheInstances().Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
|
||||
Vendored
+199
-189
@@ -10,12 +10,12 @@
|
||||
#include "cache/clock_cache.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
@@ -26,9 +26,10 @@
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/likely.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "port/lang.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/random.h"
|
||||
@@ -360,9 +361,16 @@ void ConstApplyToEntriesRange(const Func& func, const HandleImpl* begin,
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SanitizeEvictionEffortCap(int eviction_effort_cap) {
|
||||
constexpr uint32_t kStrictCapacityLimitBit = 1u << 31;
|
||||
|
||||
uint32_t SanitizeEncodeEecAndScl(int eviction_effort_cap,
|
||||
bool strict_capacit_limit) {
|
||||
eviction_effort_cap = std::max(int{1}, eviction_effort_cap);
|
||||
return static_cast<uint32_t>(eviction_effort_cap);
|
||||
eviction_effort_cap =
|
||||
std::min(static_cast<int>(~kStrictCapacityLimitBit), eviction_effort_cap);
|
||||
uint32_t eec_and_scl = static_cast<uint32_t>(eviction_effort_cap);
|
||||
eec_and_scl |= strict_capacit_limit ? kStrictCapacityLimitBit : 0;
|
||||
return eec_and_scl;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -373,22 +381,6 @@ void ClockHandleBasicData::FreeData(MemoryAllocator* allocator) const {
|
||||
}
|
||||
}
|
||||
|
||||
BaseClockTable::BaseClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
int eviction_effort_cap,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed)
|
||||
: capacity_(capacity),
|
||||
eec_and_scl_(EecAndScl{}
|
||||
.With<EvictionEffortCap>(
|
||||
SanitizeEvictionEffortCap(eviction_effort_cap))
|
||||
.With<StrictCapacityLimit>(strict_capacity_limit)),
|
||||
metadata_charge_policy_(metadata_charge_policy),
|
||||
allocator_(allocator),
|
||||
eviction_callback_(*eviction_callback),
|
||||
hash_seed_(*hash_seed) {}
|
||||
|
||||
template <class HandleImpl>
|
||||
HandleImpl* BaseClockTable::StandaloneInsert(
|
||||
const ClockHandleBasicData& proto) {
|
||||
@@ -410,7 +402,8 @@ HandleImpl* BaseClockTable::StandaloneInsert(
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
ClockHandleBasicData& proto, bool allow_uncharged) {
|
||||
ClockHandleBasicData& proto, size_t capacity, uint32_t eec_and_scl,
|
||||
bool allow_uncharged) {
|
||||
Table& derived = static_cast<Table&>(*this);
|
||||
typename Table::InsertState state;
|
||||
derived.StartInsert(state);
|
||||
@@ -419,10 +412,10 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
// NOTE: we can use eec_and_scl as eviction_effort_cap below because
|
||||
// strict_capacity_limit=true is supposed to disable the limit on eviction
|
||||
// effort, and a large value effectively does that.
|
||||
if (eec_and_scl_.LoadRelaxed().Get<StrictCapacityLimit>()) {
|
||||
if (eec_and_scl & kStrictCapacityLimitBit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict<Table>(
|
||||
total_charge,
|
||||
/*need_evict_for_occupancy=*/false, state);
|
||||
total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false, eec_and_scl, state);
|
||||
if (!s.ok()) {
|
||||
if (allow_uncharged) {
|
||||
proto.total_charge = 0;
|
||||
@@ -433,8 +426,8 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
|
||||
total_charge,
|
||||
/*need_evict_for_occupancy=*/false, state);
|
||||
total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false, eec_and_scl, state);
|
||||
if (!success) {
|
||||
// Force the issue
|
||||
usage_.FetchAddRelaxed(total_charge);
|
||||
@@ -446,9 +439,8 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
|
||||
template <class Table>
|
||||
Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
size_t total_charge, bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state) {
|
||||
const size_t capacity = capacity_.LoadRelaxed();
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap, typename Table::InsertState& state) {
|
||||
if (total_charge > capacity) {
|
||||
return Status::MemoryLimit(
|
||||
"Cache entry too large for a single cache shard: " +
|
||||
@@ -473,7 +465,8 @@ Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
}
|
||||
if (request_evict_charge > 0) {
|
||||
EvictionData data;
|
||||
static_cast<Table*>(this)->Evict(request_evict_charge, state, &data);
|
||||
static_cast<Table*>(this)->Evict(request_evict_charge, state, &data,
|
||||
eviction_effort_cap);
|
||||
occupancy_.FetchSub(data.freed_count);
|
||||
if (LIKELY(data.freed_charge > need_evict_charge)) {
|
||||
assert(data.freed_count > 0);
|
||||
@@ -502,8 +495,8 @@ Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
|
||||
template <class Table>
|
||||
inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
size_t total_charge, bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state) {
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap, typename Table::InsertState& state) {
|
||||
// For simplicity, we consider that either the cache can accept the insert
|
||||
// with no evictions, or we must evict enough to make (at least) enough
|
||||
// space. It could lead to unnecessary failures or excessive evictions in
|
||||
@@ -513,8 +506,7 @@ inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
// charge. Thus, we should evict some extra if it's not a signifcant
|
||||
// portion of the shard capacity. This can have the side benefit of
|
||||
// involving fewer threads in eviction.
|
||||
const size_t old_usage = usage_.LoadRelaxed();
|
||||
const size_t capacity = capacity_.LoadRelaxed();
|
||||
size_t old_usage = usage_.LoadRelaxed();
|
||||
size_t need_evict_charge;
|
||||
// NOTE: if total_charge > old_usage, there isn't yet enough to evict
|
||||
// `total_charge` amount. Even if we only try to evict `old_usage` amount,
|
||||
@@ -540,7 +532,8 @@ inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
}
|
||||
EvictionData data;
|
||||
if (need_evict_charge > 0) {
|
||||
static_cast<Table*>(this)->Evict(need_evict_charge, state, &data);
|
||||
static_cast<Table*>(this)->Evict(need_evict_charge, state, &data,
|
||||
eviction_effort_cap);
|
||||
// Deal with potential occupancy deficit
|
||||
if (UNLIKELY(need_evict_for_occupancy) && data.freed_count == 0) {
|
||||
assert(data.freed_charge == 0);
|
||||
@@ -576,10 +569,8 @@ void BaseClockTable::TrackAndReleaseEvictedEntry(ClockHandle* h) {
|
||||
MarkEmpty(*h);
|
||||
}
|
||||
|
||||
bool BaseClockTable::IsEvictionEffortExceeded(
|
||||
const BaseClockTable::EvictionData& data) const {
|
||||
auto eviction_effort_cap =
|
||||
eec_and_scl_.LoadRelaxed().GetEffectiveEvictionEffortCap();
|
||||
bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
// Basically checks whether the ratio of useful effort to wasted effort is
|
||||
// too low, with a start-up allowance for wasted effort before any useful
|
||||
// effort.
|
||||
@@ -590,7 +581,8 @@ bool BaseClockTable::IsEvictionEffortExceeded(
|
||||
template <class Table>
|
||||
Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle,
|
||||
Cache::Priority priority) {
|
||||
Cache::Priority priority, size_t capacity,
|
||||
uint32_t eec_and_scl) {
|
||||
using HandleImpl = typename Table::HandleImpl;
|
||||
Table& derived = static_cast<Table&>(*this);
|
||||
|
||||
@@ -611,9 +603,9 @@ Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
// NOTE: we can use eec_and_scl as eviction_effort_cap below because
|
||||
// strict_capacity_limit=true is supposed to disable the limit on eviction
|
||||
// effort, and a large value effectively does that.
|
||||
if (eec_and_scl_.LoadRelaxed().Get<StrictCapacityLimit>()) {
|
||||
if (eec_and_scl & kStrictCapacityLimitBit) {
|
||||
Status s = ChargeUsageMaybeEvictStrict<Table>(
|
||||
total_charge, need_evict_for_occupancy, state);
|
||||
total_charge, capacity, need_evict_for_occupancy, eec_and_scl, state);
|
||||
if (!s.ok()) {
|
||||
// Revert occupancy
|
||||
occupancy_.FetchSubRelaxed(1);
|
||||
@@ -622,7 +614,7 @@ Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
|
||||
total_charge, need_evict_for_occupancy, state);
|
||||
total_charge, capacity, need_evict_for_occupancy, eec_and_scl, state);
|
||||
if (!success) {
|
||||
// Revert occupancy
|
||||
occupancy_.FetchSubRelaxed(1);
|
||||
@@ -726,13 +718,11 @@ void BaseClockTable::TEST_ReleaseNMinus1(ClockHandle* h, size_t n) {
|
||||
#endif
|
||||
|
||||
FixedHyperClockTable::FixedHyperClockTable(
|
||||
size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
size_t capacity, CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
: BaseClockTable(capacity, strict_capacity_limit, opts.eviction_effort_cap,
|
||||
metadata_charge_policy, allocator, eviction_callback,
|
||||
: BaseClockTable(metadata_charge_policy, allocator, eviction_callback,
|
||||
hash_seed),
|
||||
length_bits_(CalcHashBits(capacity, opts.estimated_value_size,
|
||||
metadata_charge_policy)),
|
||||
@@ -1123,7 +1113,8 @@ inline void FixedHyperClockTable::ReclaimEntryUsage(size_t total_charge) {
|
||||
}
|
||||
|
||||
inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
|
||||
EvictionData* data) {
|
||||
EvictionData* data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
// precondition
|
||||
assert(requested_charge > 0);
|
||||
|
||||
@@ -1158,7 +1149,7 @@ inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
|
||||
if (old_clock_pointer >= max_clock_pointer) {
|
||||
return;
|
||||
}
|
||||
if (IsEvictionEffortExceeded(*data)) {
|
||||
if (IsEvictionEffortExceeded(*data, eviction_effort_cap)) {
|
||||
eviction_effort_exceeded_count_.FetchAddRelaxed(1);
|
||||
return;
|
||||
}
|
||||
@@ -1176,11 +1167,14 @@ ClockCacheShard<Table>::ClockCacheShard(
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const typename Table::Opts& opts)
|
||||
: CacheShardBase(metadata_charge_policy),
|
||||
table_(capacity, strict_capacity_limit, metadata_charge_policy, allocator,
|
||||
eviction_callback, hash_seed, opts) {
|
||||
table_(capacity, metadata_charge_policy, allocator, eviction_callback,
|
||||
hash_seed, opts),
|
||||
capacity_(capacity),
|
||||
eec_and_scl_(SanitizeEncodeEecAndScl(opts.eviction_effort_cap,
|
||||
strict_capacity_limit)) {
|
||||
// Initial charge metadata should not exceed capacity
|
||||
assert(table_.GetUsage() <= table_.GetCapacity() ||
|
||||
table_.GetCapacity() < sizeof(HandleImpl));
|
||||
assert(table_.GetUsage() <= capacity_.LoadRelaxed() ||
|
||||
capacity_.LoadRelaxed() < sizeof(HandleImpl));
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1246,14 +1240,18 @@ int FixedHyperClockTable::CalcHashBits(
|
||||
|
||||
template <class Table>
|
||||
void ClockCacheShard<Table>::SetCapacity(size_t capacity) {
|
||||
table_.SetCapacity(capacity);
|
||||
capacity_.StoreRelaxed(capacity);
|
||||
// next Insert will take care of any necessary evictions
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
void ClockCacheShard<Table>::SetStrictCapacityLimit(
|
||||
bool strict_capacity_limit) {
|
||||
table_.SetStrictCapacityLimit(strict_capacity_limit);
|
||||
if (strict_capacity_limit) {
|
||||
eec_and_scl_.FetchOrRelaxed(kStrictCapacityLimitBit);
|
||||
} else {
|
||||
eec_and_scl_.FetchAndRelaxed(~kStrictCapacityLimitBit);
|
||||
}
|
||||
// next Insert will take care of any necessary evictions
|
||||
}
|
||||
|
||||
@@ -1273,7 +1271,9 @@ Status ClockCacheShard<Table>::Insert(const Slice& key,
|
||||
proto.value = value;
|
||||
proto.helper = helper;
|
||||
proto.total_charge = charge;
|
||||
return table_.template Insert<Table>(proto, handle, priority);
|
||||
return table_.template Insert<Table>(proto, handle, priority,
|
||||
capacity_.LoadRelaxed(),
|
||||
eec_and_scl_.LoadRelaxed());
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1288,7 +1288,9 @@ typename Table::HandleImpl* ClockCacheShard<Table>::CreateStandalone(
|
||||
proto.value = obj;
|
||||
proto.helper = helper;
|
||||
proto.total_charge = charge;
|
||||
return table_.template CreateStandalone<Table>(proto, allow_uncharged);
|
||||
return table_.template CreateStandalone<Table>(proto, capacity_.LoadRelaxed(),
|
||||
eec_and_scl_.LoadRelaxed(),
|
||||
allow_uncharged);
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1357,7 +1359,7 @@ size_t ClockCacheShard<Table>::GetStandaloneUsage() const {
|
||||
|
||||
template <class Table>
|
||||
size_t ClockCacheShard<Table>::GetCapacity() const {
|
||||
return table_.GetCapacity();
|
||||
return capacity_.LoadRelaxed();
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1725,13 +1727,10 @@ inline uint64_t UsedLengthToLengthInfo(size_t used_length) {
|
||||
return length_info;
|
||||
}
|
||||
|
||||
// Avoid potential initialization order race with port::kPageSize
|
||||
constexpr size_t kPresumedPageSize = 4096;
|
||||
|
||||
inline size_t GetStartingLength(size_t capacity) {
|
||||
if (capacity > kPresumedPageSize) {
|
||||
if (capacity > port::kPageSize) {
|
||||
// Start with one memory page
|
||||
return kPresumedPageSize / sizeof(AutoHyperClockTable::HandleImpl);
|
||||
return port::kPageSize / sizeof(AutoHyperClockTable::HandleImpl);
|
||||
} else {
|
||||
// Mostly to make unit tests happy
|
||||
return 4;
|
||||
@@ -1752,6 +1751,26 @@ inline void GetHomeIndexAndShift(uint64_t length_info, uint64_t hash,
|
||||
assert(*home < LengthInfoToUsedLength(length_info));
|
||||
}
|
||||
|
||||
inline int GetShiftFromNextWithShift(uint64_t next_with_shift) {
|
||||
return BitwiseAnd(next_with_shift,
|
||||
AutoHyperClockTable::HandleImpl::kShiftMask);
|
||||
}
|
||||
|
||||
inline size_t GetNextFromNextWithShift(uint64_t next_with_shift) {
|
||||
return static_cast<size_t>(next_with_shift >>
|
||||
AutoHyperClockTable::HandleImpl::kNextShift);
|
||||
}
|
||||
|
||||
inline uint64_t MakeNextWithShift(size_t next, int shift) {
|
||||
return (uint64_t{next} << AutoHyperClockTable::HandleImpl::kNextShift) |
|
||||
static_cast<uint64_t>(shift);
|
||||
}
|
||||
|
||||
inline uint64_t MakeNextWithShiftEnd(size_t head, int shift) {
|
||||
return AutoHyperClockTable::HandleImpl::kNextEndFlags |
|
||||
MakeNextWithShift(head, shift);
|
||||
}
|
||||
|
||||
// Helper function for Lookup
|
||||
inline bool MatchAndRef(const UniqueId64x2* hashed_key, const ClockHandle& h,
|
||||
int shift = 0, size_t home = 0,
|
||||
@@ -1801,39 +1820,36 @@ inline bool MatchAndRef(const UniqueId64x2* hashed_key, const ClockHandle& h,
|
||||
}
|
||||
}
|
||||
|
||||
using NextWithShift = AutoHyperClockTable::HandleImpl::NextWithShift;
|
||||
|
||||
// Assumes a chain rewrite lock prevents concurrent modification of
|
||||
// these chain pointers
|
||||
void UpgradeShiftsOnRange(AutoHyperClockTable::HandleImpl* arr,
|
||||
size_t& frontier,
|
||||
NextWithShift stop_before_or_new_tail, int old_shift,
|
||||
int new_shift) {
|
||||
size_t& frontier, uint64_t stop_before_or_new_tail,
|
||||
int old_shift, int new_shift) {
|
||||
assert(frontier != SIZE_MAX);
|
||||
assert(new_shift == old_shift + 1);
|
||||
(void)old_shift;
|
||||
(void)new_shift;
|
||||
using HandleImpl = AutoHyperClockTable::HandleImpl;
|
||||
for (;;) {
|
||||
NextWithShift next_with_shift = arr[frontier].chain_next_with_shift.Load();
|
||||
assert(next_with_shift.GetShift() == old_shift);
|
||||
uint64_t next_with_shift = arr[frontier].chain_next_with_shift.Load();
|
||||
assert(GetShiftFromNextWithShift(next_with_shift) == old_shift);
|
||||
if (next_with_shift == stop_before_or_new_tail) {
|
||||
// Stopping at entry with pointer matching "stop before"
|
||||
assert(!next_with_shift.IsEnd());
|
||||
assert(!HandleImpl::IsEnd(next_with_shift));
|
||||
return;
|
||||
}
|
||||
if (next_with_shift.IsEnd()) {
|
||||
if (HandleImpl::IsEnd(next_with_shift)) {
|
||||
// Also update tail to new tail
|
||||
assert(stop_before_or_new_tail.IsEnd());
|
||||
assert(HandleImpl::IsEnd(stop_before_or_new_tail));
|
||||
arr[frontier].chain_next_with_shift.Store(stop_before_or_new_tail);
|
||||
// Mark nothing left to upgrade
|
||||
frontier = SIZE_MAX;
|
||||
return;
|
||||
}
|
||||
// Next is another entry to process, so upgrade and advance frontier
|
||||
arr[frontier].chain_next_with_shift.Apply(
|
||||
NextWithShift::Shift::PlusTransformPromiseNoOverflow(1U));
|
||||
assert(next_with_shift.GetShift() + 1 == new_shift);
|
||||
frontier = next_with_shift.GetNext();
|
||||
arr[frontier].chain_next_with_shift.FetchAdd(1U);
|
||||
assert(GetShiftFromNextWithShift(next_with_shift + 1) == new_shift);
|
||||
frontier = GetNextFromNextWithShift(next_with_shift);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1871,19 +1887,19 @@ class AutoHyperClockTable::ChainRewriteLock {
|
||||
// RAII wrap existing lock held (or end)
|
||||
explicit ChainRewriteLock(HandleImpl* h,
|
||||
RelaxedAtomic<uint64_t>& /*yield_count*/,
|
||||
NextWithShift already_locked_or_end)
|
||||
uint64_t already_locked_or_end)
|
||||
: head_ptr_(&h->head_next_with_shift) {
|
||||
saved_head_ = already_locked_or_end;
|
||||
// already locked or end
|
||||
assert(saved_head_.IsLocked());
|
||||
assert(saved_head_ & HandleImpl::kHeadLocked);
|
||||
}
|
||||
|
||||
~ChainRewriteLock() {
|
||||
if (!IsEnd()) {
|
||||
// Release lock
|
||||
NextWithShift old;
|
||||
head_ptr_->Apply(NextWithShift::LockedFlag::ClearTransform(), &old);
|
||||
assert(old.IsLockedNotEnd());
|
||||
uint64_t old = head_ptr_->FetchAnd(~HandleImpl::kHeadLocked);
|
||||
(void)old;
|
||||
assert((old & HandleImpl::kNextEndFlags) == HandleImpl::kHeadLocked);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1893,13 +1909,12 @@ class AutoHyperClockTable::ChainRewriteLock {
|
||||
}
|
||||
|
||||
// Expected current state, assuming no parallel updates.
|
||||
NextWithShift GetSavedHead() const { return saved_head_; }
|
||||
uint64_t GetSavedHead() const { return saved_head_; }
|
||||
|
||||
bool CasUpdate(NextWithShift next_with_shift,
|
||||
bool CasUpdate(uint64_t next_with_shift,
|
||||
RelaxedAtomic<uint64_t>& yield_count) {
|
||||
NextWithShift new_head =
|
||||
next_with_shift.With<NextWithShift::LockedFlag>(true);
|
||||
NextWithShift expected = GetSavedHead();
|
||||
uint64_t new_head = next_with_shift | HandleImpl::kHeadLocked;
|
||||
uint64_t expected = GetSavedHead();
|
||||
bool success = head_ptr_->CasStrong(expected, new_head);
|
||||
if (success) {
|
||||
// Ensure IsEnd() is kept up-to-date, including for dtor
|
||||
@@ -1908,7 +1923,7 @@ class AutoHyperClockTable::ChainRewriteLock {
|
||||
// Parallel update to head, such as Insert()
|
||||
if (IsEnd()) {
|
||||
// Didn't previously hold a lock
|
||||
if (expected.IsEnd()) {
|
||||
if (HandleImpl::IsEnd(expected)) {
|
||||
// Still don't need to
|
||||
saved_head_ = expected;
|
||||
} else {
|
||||
@@ -1917,25 +1932,28 @@ class AutoHyperClockTable::ChainRewriteLock {
|
||||
}
|
||||
} else {
|
||||
// Parallel update must preserve our lock
|
||||
assert(expected.IsLockedNotEnd());
|
||||
assert((expected & HandleImpl::kNextEndFlags) ==
|
||||
HandleImpl::kHeadLocked);
|
||||
saved_head_ = expected;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool IsEnd() const { return saved_head_.IsEnd(); }
|
||||
bool IsEnd() const { return HandleImpl::IsEnd(saved_head_); }
|
||||
|
||||
private:
|
||||
void Acquire(RelaxedAtomic<uint64_t>& yield_count) {
|
||||
for (;;) {
|
||||
// Acquire removal lock on the chain
|
||||
NextWithShift old_head;
|
||||
head_ptr_->Apply(NextWithShift::LockedFlag::SetTransform(), &old_head,
|
||||
&saved_head_);
|
||||
if (!old_head.IsLockedNotEnd()) {
|
||||
uint64_t old_head = head_ptr_->FetchOr(HandleImpl::kHeadLocked);
|
||||
if ((old_head & HandleImpl::kNextEndFlags) != HandleImpl::kHeadLocked) {
|
||||
// Either acquired the lock or lock not needed (end)
|
||||
assert(old_head.IsEnd() == old_head.IsLocked());
|
||||
assert((old_head & HandleImpl::kNextEndFlags) == 0 ||
|
||||
(old_head & HandleImpl::kNextEndFlags) ==
|
||||
HandleImpl::kNextEndFlags);
|
||||
|
||||
saved_head_ = old_head | HandleImpl::kHeadLocked;
|
||||
break;
|
||||
}
|
||||
// NOTE: one of the few yield-wait loops, which is rare enough in practice
|
||||
@@ -1946,18 +1964,16 @@ class AutoHyperClockTable::ChainRewriteLock {
|
||||
}
|
||||
}
|
||||
|
||||
AcqRelBitFieldsAtomic<NextWithShift>* head_ptr_;
|
||||
NextWithShift saved_head_;
|
||||
AcqRelAtomic<uint64_t>* head_ptr_;
|
||||
uint64_t saved_head_;
|
||||
};
|
||||
|
||||
AutoHyperClockTable::AutoHyperClockTable(
|
||||
size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
size_t capacity, CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
: BaseClockTable(capacity, strict_capacity_limit, opts.eviction_effort_cap,
|
||||
metadata_charge_policy, allocator, eviction_callback,
|
||||
: BaseClockTable(metadata_charge_policy, allocator, eviction_callback,
|
||||
hash_seed),
|
||||
array_(MemMapping::AllocateLazyZeroed(
|
||||
sizeof(HandleImpl) * CalcMaxUsableLength(capacity,
|
||||
@@ -1969,11 +1985,6 @@ AutoHyperClockTable::AutoHyperClockTable(
|
||||
grow_frontier_(GetTableSize()),
|
||||
clock_pointer_mask_(
|
||||
BottomNBits(UINT64_MAX, LengthInfoToMinShift(length_info_.Load()))) {
|
||||
if (array_.Get() == nullptr) {
|
||||
fprintf(stderr,
|
||||
"Anonymous mmap for RocksDB HyperClockCache failed. Aborting.\n");
|
||||
std::terminate();
|
||||
}
|
||||
if (metadata_charge_policy ==
|
||||
CacheMetadataChargePolicy::kFullChargeCacheMetadata) {
|
||||
// NOTE: ignoring page boundaries for simplicity
|
||||
@@ -2002,9 +2013,9 @@ AutoHyperClockTable::AutoHyperClockTable(
|
||||
#endif
|
||||
if (major + i < used_length) {
|
||||
array_[i].head_next_with_shift.StoreRelaxed(
|
||||
NextWithShift::MakeEnd(i, max_shift));
|
||||
MakeNextWithShiftEnd(i, max_shift));
|
||||
array_[major + i].head_next_with_shift.StoreRelaxed(
|
||||
NextWithShift::MakeEnd(major + i, max_shift));
|
||||
MakeNextWithShiftEnd(major + i, max_shift));
|
||||
#ifndef NDEBUG // Extra invariant checking
|
||||
GetHomeIndexAndShift(length_info, i, &home, &shift);
|
||||
assert(home == i);
|
||||
@@ -2015,7 +2026,7 @@ AutoHyperClockTable::AutoHyperClockTable(
|
||||
#endif
|
||||
} else {
|
||||
array_[i].head_next_with_shift.StoreRelaxed(
|
||||
NextWithShift::MakeEnd(i, min_shift));
|
||||
MakeNextWithShiftEnd(i, min_shift));
|
||||
#ifndef NDEBUG // Extra invariant checking
|
||||
GetHomeIndexAndShift(length_info, i, &home, &shift);
|
||||
assert(home == i);
|
||||
@@ -2041,23 +2052,15 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
HandleImpl::kUnusedMarker) {
|
||||
used_end++;
|
||||
}
|
||||
// This check can be extra expensive for a cache that is just created,
|
||||
// maybe used for a small number of entries, as in a unit test, and then
|
||||
// destroyed. Only do this in rare modes. REVISED: Don't scan the whole mmap,
|
||||
// just a reasonable frontier past what we expect to have written.
|
||||
#ifdef MUST_FREE_HEAP_ALLOCATIONS
|
||||
for (size_t i = used_end; i < array_.Count() && i < used_end + 64U; i++) {
|
||||
assert(array_[i].head_next_with_shift.LoadRelaxed() ==
|
||||
HandleImpl::kUnusedMarker);
|
||||
assert(array_[i].chain_next_with_shift.LoadRelaxed() ==
|
||||
HandleImpl::kUnusedMarker);
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = used_end; i < array_.Count(); i++) {
|
||||
assert(array_[i].head_next_with_shift.LoadRelaxed() == 0);
|
||||
assert(array_[i].chain_next_with_shift.LoadRelaxed() == 0);
|
||||
assert(array_[i].meta.LoadRelaxed() == 0);
|
||||
}
|
||||
#endif // MUST_FREE_HEAP_ALLOCATIONS
|
||||
#ifndef NDEBUG // Extra invariant checking
|
||||
std::vector<bool> was_populated(used_end);
|
||||
std::vector<bool> was_pointed_to(used_end);
|
||||
#endif // !NDEBUG
|
||||
#endif
|
||||
for (size_t i = 0; i < used_end; i++) {
|
||||
HandleImpl& h = array_[i];
|
||||
switch (h.meta.LoadRelaxed() >> ClockHandle::kStateShift) {
|
||||
@@ -2072,13 +2075,15 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
usage_.FetchSubRelaxed(h.total_charge);
|
||||
occupancy_.FetchSubRelaxed(1U);
|
||||
was_populated[i] = true;
|
||||
if (!h.chain_next_with_shift.LoadRelaxed().IsEnd()) {
|
||||
assert(!h.chain_next_with_shift.LoadRelaxed().IsLocked());
|
||||
size_t next = h.chain_next_with_shift.LoadRelaxed().GetNext();
|
||||
if (!HandleImpl::IsEnd(h.chain_next_with_shift.LoadRelaxed())) {
|
||||
assert((h.chain_next_with_shift.LoadRelaxed() &
|
||||
HandleImpl::kHeadLocked) == 0);
|
||||
size_t next =
|
||||
GetNextFromNextWithShift(h.chain_next_with_shift.LoadRelaxed());
|
||||
assert(!was_pointed_to[next]);
|
||||
was_pointed_to[next] = true;
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
#endif
|
||||
break;
|
||||
// otherwise
|
||||
default:
|
||||
@@ -2086,12 +2091,13 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
break;
|
||||
}
|
||||
#ifndef NDEBUG // Extra invariant checking
|
||||
if (!h.head_next_with_shift.LoadRelaxed().IsEnd()) {
|
||||
size_t next = h.head_next_with_shift.LoadRelaxed().GetNext();
|
||||
if (!HandleImpl::IsEnd(h.head_next_with_shift.LoadRelaxed())) {
|
||||
size_t next =
|
||||
GetNextFromNextWithShift(h.head_next_with_shift.LoadRelaxed());
|
||||
assert(!was_pointed_to[next]);
|
||||
was_pointed_to[next] = true;
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
#endif
|
||||
}
|
||||
#ifndef NDEBUG // Extra invariant checking
|
||||
// This check is not perfect, but should detect most reasonable cases
|
||||
@@ -2104,7 +2110,7 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
assert(!was_pointed_to[i]);
|
||||
}
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
#endif
|
||||
|
||||
// Metadata charging only follows the published table size
|
||||
assert(usage_.LoadRelaxed() == 0 ||
|
||||
@@ -2202,10 +2208,10 @@ bool AutoHyperClockTable::Grow(InsertState& state) {
|
||||
// chain rewrite lock has been released.
|
||||
size_t old_old_home = BottomNBits(grow_home, old_shift - 1);
|
||||
for (;;) {
|
||||
NextWithShift old_old_head =
|
||||
array_[old_old_home].head_next_with_shift.Load();
|
||||
if (old_old_head.GetShift() >= old_shift) {
|
||||
if (!old_old_head.IsLockedNotEnd()) {
|
||||
uint64_t old_old_head = array_[old_old_home].head_next_with_shift.Load();
|
||||
if (GetShiftFromNextWithShift(old_old_head) >= old_shift) {
|
||||
if ((old_old_head & HandleImpl::kNextEndFlags) !=
|
||||
HandleImpl::kHeadLocked) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2265,7 +2271,8 @@ void AutoHyperClockTable::CatchUpLengthInfoNoWait(
|
||||
if (published_usable_size < known_usable_grow_home) {
|
||||
int old_shift = FloorLog2(next_usable_size - 1);
|
||||
size_t old_home = BottomNBits(published_usable_size, old_shift);
|
||||
int shift = array_[old_home].head_next_with_shift.Load().GetShift();
|
||||
int shift = GetShiftFromNextWithShift(
|
||||
array_[old_home].head_next_with_shift.Load());
|
||||
if (shift <= old_shift) {
|
||||
// Not ready
|
||||
break;
|
||||
@@ -2416,10 +2423,9 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
ChainRewriteLock zero_head_lock(&arr[old_home], yield_count_);
|
||||
|
||||
// Used for locking the one chain below
|
||||
NextWithShift saved_one_head;
|
||||
uint64_t saved_one_head;
|
||||
// One head has not been written to
|
||||
assert(arr[grow_home].head_next_with_shift.Load() ==
|
||||
HandleImpl::kUnusedMarker);
|
||||
assert(arr[grow_home].head_next_with_shift.Load() == 0);
|
||||
|
||||
// old_home will also the head of the new "zero chain" -- all entries in the
|
||||
// "from" chain whose next hash bit is 0. grow_home will be head of the new
|
||||
@@ -2441,7 +2447,7 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
assert(cur == SIZE_MAX);
|
||||
assert(chain_frontier_first == -1);
|
||||
|
||||
NextWithShift next_with_shift = zero_head_lock.GetSavedHead();
|
||||
uint64_t next_with_shift = zero_head_lock.GetSavedHead();
|
||||
|
||||
// Find a single representative for each target chain, or scan the whole
|
||||
// chain if some target chain has no representative.
|
||||
@@ -2454,16 +2460,16 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
assert((cur == SIZE_MAX) == (zero_chain_frontier == SIZE_MAX &&
|
||||
one_chain_frontier == SIZE_MAX));
|
||||
|
||||
assert(next_with_shift.GetShift() == old_shift);
|
||||
assert(GetShiftFromNextWithShift(next_with_shift) == old_shift);
|
||||
|
||||
// Check for end of original chain
|
||||
if (next_with_shift.IsEnd()) {
|
||||
if (HandleImpl::IsEnd(next_with_shift)) {
|
||||
cur = SIZE_MAX;
|
||||
break;
|
||||
}
|
||||
|
||||
// next_with_shift is not End
|
||||
cur = next_with_shift.GetNext();
|
||||
cur = GetNextFromNextWithShift(next_with_shift);
|
||||
|
||||
if (BottomNBits(arr[cur].hashed_key[1], new_shift) == old_home) {
|
||||
// Entry for zero chain
|
||||
@@ -2502,10 +2508,10 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
(zero_chain_frontier == SIZE_MAX && one_chain_frontier == SIZE_MAX));
|
||||
|
||||
// Always update one chain's head first (safe), and mark it as locked
|
||||
saved_one_head = one_chain_frontier != SIZE_MAX
|
||||
? NextWithShift::Make(one_chain_frontier, new_shift)
|
||||
: NextWithShift::MakeEnd(grow_home, new_shift);
|
||||
saved_one_head.Set<NextWithShift::LockedFlag>(true);
|
||||
saved_one_head = HandleImpl::kHeadLocked |
|
||||
(one_chain_frontier != SIZE_MAX
|
||||
? MakeNextWithShift(one_chain_frontier, new_shift)
|
||||
: MakeNextWithShiftEnd(grow_home, new_shift));
|
||||
arr[grow_home].head_next_with_shift.Store(saved_one_head);
|
||||
|
||||
// Make sure length_info_ hasn't been updated too early, as we're about
|
||||
@@ -2515,8 +2521,8 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
// Try to set zero's head.
|
||||
if (zero_head_lock.CasUpdate(
|
||||
zero_chain_frontier != SIZE_MAX
|
||||
? NextWithShift::Make(zero_chain_frontier, new_shift)
|
||||
: NextWithShift::MakeEnd(old_home, new_shift),
|
||||
? MakeNextWithShift(zero_chain_frontier, new_shift)
|
||||
: MakeNextWithShiftEnd(old_home, new_shift),
|
||||
yield_count_)) {
|
||||
// Both heads successfully updated to new shift
|
||||
break;
|
||||
@@ -2550,10 +2556,10 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
size_t& other_frontier = chain_frontier_first != 0
|
||||
? /*&*/ zero_chain_frontier
|
||||
: /*&*/ one_chain_frontier;
|
||||
NextWithShift stop_before_or_new_tail =
|
||||
uint64_t stop_before_or_new_tail =
|
||||
other_frontier != SIZE_MAX
|
||||
? /*stop before*/ NextWithShift::Make(other_frontier, old_shift)
|
||||
: /*new tail*/ NextWithShift::MakeEnd(
|
||||
? /*stop before*/ MakeNextWithShift(other_frontier, old_shift)
|
||||
: /*new tail*/ MakeNextWithShiftEnd(
|
||||
chain_frontier_first == 0 ? old_home : grow_home, new_shift);
|
||||
UpgradeShiftsOnRange(arr, first_frontier, stop_before_or_new_tail,
|
||||
old_shift, new_shift);
|
||||
@@ -2579,19 +2585,20 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
? /*&*/ zero_chain_frontier
|
||||
: /*&*/ one_chain_frontier;
|
||||
assert(cur != first_frontier);
|
||||
assert(arr[first_frontier].chain_next_with_shift.Load().GetNext() ==
|
||||
assert(GetNextFromNextWithShift(
|
||||
arr[first_frontier].chain_next_with_shift.Load()) ==
|
||||
other_frontier);
|
||||
|
||||
NextWithShift next_with_shift = arr[cur].chain_next_with_shift.Load();
|
||||
uint64_t next_with_shift = arr[cur].chain_next_with_shift.Load();
|
||||
|
||||
// Check for end of original chain
|
||||
if (next_with_shift.IsEnd()) {
|
||||
if (HandleImpl::IsEnd(next_with_shift)) {
|
||||
// Can set upgraded tail on first chain
|
||||
NextWithShift first_new_tail = NextWithShift::MakeEnd(
|
||||
uint64_t first_new_tail = MakeNextWithShiftEnd(
|
||||
chain_frontier_first == 0 ? old_home : grow_home, new_shift);
|
||||
arr[first_frontier].chain_next_with_shift.Store(first_new_tail);
|
||||
// And upgrade remainder of other chain
|
||||
NextWithShift other_new_tail = NextWithShift::MakeEnd(
|
||||
uint64_t other_new_tail = MakeNextWithShiftEnd(
|
||||
chain_frontier_first != 0 ? old_home : grow_home, new_shift);
|
||||
UpgradeShiftsOnRange(arr, other_frontier, other_new_tail, old_shift,
|
||||
new_shift);
|
||||
@@ -2600,7 +2607,7 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
}
|
||||
|
||||
// next_with_shift is not End
|
||||
cur = next_with_shift.GetNext();
|
||||
cur = GetNextFromNextWithShift(next_with_shift);
|
||||
|
||||
int target_chain;
|
||||
if (BottomNBits(arr[cur].hashed_key[1], new_shift) == old_home) {
|
||||
@@ -2613,7 +2620,7 @@ void AutoHyperClockTable::SplitForGrow(size_t grow_home, size_t old_home,
|
||||
}
|
||||
if (target_chain == chain_frontier_first) {
|
||||
// Found next entry to skip to on the first chain
|
||||
NextWithShift skip_to = NextWithShift::Make(cur, new_shift);
|
||||
uint64_t skip_to = MakeNextWithShift(cur, new_shift);
|
||||
arr[first_frontier].chain_next_with_shift.Store(skip_to);
|
||||
first_frontier = cur;
|
||||
// Upgrade other chain up to entry before that one
|
||||
@@ -2654,17 +2661,17 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
|
||||
HandleImpl* const arr = array_.Get();
|
||||
|
||||
NextWithShift next_with_shift = rewrite_lock.GetSavedHead();
|
||||
assert(!next_with_shift.IsEnd());
|
||||
int home_shift = next_with_shift.GetShift();
|
||||
uint64_t next_with_shift = rewrite_lock.GetSavedHead();
|
||||
assert(!HandleImpl::IsEnd(next_with_shift));
|
||||
int home_shift = GetShiftFromNextWithShift(next_with_shift);
|
||||
(void)home;
|
||||
(void)home_shift;
|
||||
size_t next = next_with_shift.GetNext();
|
||||
size_t next = GetNextFromNextWithShift(next_with_shift);
|
||||
assert(next < array_.Count());
|
||||
HandleImpl* h = &arr[next];
|
||||
HandleImpl* prev_to_keep = nullptr;
|
||||
#ifndef NDEBUG
|
||||
NextWithShift prev_to_keep_next_with_shift{};
|
||||
uint64_t prev_to_keep_next_with_shift = 0;
|
||||
#endif
|
||||
// Whether there are entries between h and prev_to_keep that should be
|
||||
// purged from the chain.
|
||||
@@ -2722,13 +2729,13 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
// update any new entries just inserted in parallel.
|
||||
// Can simply restart (GetSavedHead() already updated from CAS failure).
|
||||
next_with_shift = rewrite_lock.GetSavedHead();
|
||||
assert(!next_with_shift.IsEnd());
|
||||
next = next_with_shift.GetNext();
|
||||
assert(!HandleImpl::IsEnd(next_with_shift));
|
||||
next = GetNextFromNextWithShift(next_with_shift);
|
||||
assert(next < array_.Count());
|
||||
h = &arr[next];
|
||||
pending_purge = false;
|
||||
assert(prev_to_keep == nullptr);
|
||||
assert(next_with_shift.GetShift() == home_shift);
|
||||
assert(GetShiftFromNextWithShift(next_with_shift) == home_shift);
|
||||
continue;
|
||||
}
|
||||
pending_purge = false;
|
||||
@@ -2750,13 +2757,13 @@ void AutoHyperClockTable::PurgeImplLocked(OpData* op_data,
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(next_with_shift.GetShift() == home_shift);
|
||||
assert(GetShiftFromNextWithShift(next_with_shift) == home_shift);
|
||||
|
||||
// Check for end marker
|
||||
if (next_with_shift.IsEnd()) {
|
||||
if (HandleImpl::IsEnd(next_with_shift)) {
|
||||
h = nullptr;
|
||||
} else {
|
||||
next = next_with_shift.GetNext();
|
||||
next = GetNextFromNextWithShift(next_with_shift);
|
||||
assert(next < array_.Count());
|
||||
h = &arr[next];
|
||||
assert(h != prev_to_keep);
|
||||
@@ -2828,7 +2835,7 @@ void AutoHyperClockTable::PurgeImpl(OpData* op_data, size_t home,
|
||||
// Ensure we are at the correct home for the shift in effect for the
|
||||
// chain head.
|
||||
for (;;) {
|
||||
int shift = rewrite_lock.GetSavedHead().GetShift();
|
||||
int shift = GetShiftFromNextWithShift(rewrite_lock.GetSavedHead());
|
||||
|
||||
if (shift > home_shift) {
|
||||
// Found a newer shift at candidate head, which must apply to us.
|
||||
@@ -3024,14 +3031,14 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::DoInsert(
|
||||
}
|
||||
|
||||
// Now insert into chain using head pointer
|
||||
NextWithShift next_with_shift;
|
||||
uint64_t next_with_shift;
|
||||
int home_shift = orig_home_shift;
|
||||
|
||||
// Might need to retry
|
||||
for (int i = 0;; ++i) {
|
||||
CHECK_TOO_MANY_ITERATIONS(i);
|
||||
next_with_shift = arr[home].head_next_with_shift.Load();
|
||||
int shift = next_with_shift.GetShift();
|
||||
int shift = GetShiftFromNextWithShift(next_with_shift);
|
||||
|
||||
if (UNLIKELY(shift != home_shift)) {
|
||||
// NOTE: shift increases with table growth
|
||||
@@ -3058,14 +3065,15 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::DoInsert(
|
||||
}
|
||||
|
||||
// Values to update to
|
||||
NextWithShift head_next_with_shift = NextWithShift::Make(idx, home_shift);
|
||||
NextWithShift chain_next_with_shift = next_with_shift;
|
||||
uint64_t head_next_with_shift = MakeNextWithShift(idx, home_shift);
|
||||
uint64_t chain_next_with_shift = next_with_shift;
|
||||
|
||||
// Preserve the locked state in head, without propagating to chain next
|
||||
// where it is meaningless (and not allowed)
|
||||
if (UNLIKELY(next_with_shift.IsLockedNotEnd())) {
|
||||
head_next_with_shift.Set<NextWithShift::LockedFlag>(true);
|
||||
chain_next_with_shift.Set<NextWithShift::LockedFlag>(false);
|
||||
if (UNLIKELY((next_with_shift & HandleImpl::kNextEndFlags) ==
|
||||
HandleImpl::kHeadLocked)) {
|
||||
head_next_with_shift |= HandleImpl::kHeadLocked;
|
||||
chain_next_with_shift &= ~HandleImpl::kHeadLocked;
|
||||
}
|
||||
|
||||
arr[idx].chain_next_with_shift.Store(chain_next_with_shift);
|
||||
@@ -3134,9 +3142,9 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
|
||||
// of a loop as possible.
|
||||
|
||||
HandleImpl* const arr = array_.Get();
|
||||
NextWithShift next_with_shift = arr[home].head_next_with_shift.LoadRelaxed();
|
||||
for (size_t i = 0; !next_with_shift.IsEnd() && i < 10; ++i) {
|
||||
HandleImpl* h = &arr[next_with_shift.IsEnd()];
|
||||
uint64_t next_with_shift = arr[home].head_next_with_shift.LoadRelaxed();
|
||||
for (size_t i = 0; !HandleImpl::IsEnd(next_with_shift) && i < 10; ++i) {
|
||||
HandleImpl* h = &arr[GetNextFromNextWithShift(next_with_shift)];
|
||||
// Attempt cheap key match without acquiring a read ref. This could give a
|
||||
// false positive, which is re-checked after acquiring read ref, or false
|
||||
// negative, which is re-checked in the full Lookup. Also, this is a
|
||||
@@ -3181,7 +3189,7 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
|
||||
// Read head or chain pointer
|
||||
next_with_shift = h ? h->chain_next_with_shift.Load()
|
||||
: arr[home].head_next_with_shift.Load();
|
||||
int shift = next_with_shift.GetShift();
|
||||
int shift = GetShiftFromNextWithShift(next_with_shift);
|
||||
|
||||
// Make sure it's usable
|
||||
size_t effective_home = home;
|
||||
@@ -3235,10 +3243,10 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
|
||||
}
|
||||
|
||||
// Check for end marker
|
||||
if (next_with_shift.IsEnd()) {
|
||||
if (HandleImpl::IsEnd(next_with_shift)) {
|
||||
// To ensure we didn't miss anything in the chain, the end marker must
|
||||
// point back to the correct home.
|
||||
if (LIKELY(next_with_shift.GetNext() == effective_home)) {
|
||||
if (LIKELY(GetNextFromNextWithShift(next_with_shift) == effective_home)) {
|
||||
// Complete, clean iteration of the chain, not found.
|
||||
// Clean up.
|
||||
if (read_ref_on_chain) {
|
||||
@@ -3254,7 +3262,7 @@ AutoHyperClockTable::HandleImpl* AutoHyperClockTable::Lookup(
|
||||
}
|
||||
|
||||
// Follow the next and check for full key match, home match, or neither
|
||||
h = &arr[next_with_shift.GetNext()];
|
||||
h = &arr[GetNextFromNextWithShift(next_with_shift)];
|
||||
bool full_match_or_unknown = false;
|
||||
if (MatchAndRef(&hashed_key, *h, shift, effective_home,
|
||||
&full_match_or_unknown)) {
|
||||
@@ -3460,7 +3468,8 @@ void AutoHyperClockTable::EraseUnRefEntries() {
|
||||
}
|
||||
|
||||
void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
|
||||
EvictionData* data) {
|
||||
EvictionData* data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
// precondition
|
||||
assert(requested_charge > 0);
|
||||
|
||||
@@ -3552,7 +3561,7 @@ void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsEvictionEffortExceeded(*data)) {
|
||||
if (IsEvictionEffortExceeded(*data, eviction_effort_cap)) {
|
||||
eviction_effort_exceeded_count_.FetchAddRelaxed(1);
|
||||
return;
|
||||
}
|
||||
@@ -3570,7 +3579,7 @@ size_t AutoHyperClockTable::CalcMaxUsableLength(
|
||||
size_t num_slots =
|
||||
static_cast<size_t>(capacity / min_avg_slot_charge + 0.999999);
|
||||
|
||||
const size_t slots_per_page = kPresumedPageSize / sizeof(HandleImpl);
|
||||
const size_t slots_per_page = port::kPageSize / sizeof(HandleImpl);
|
||||
|
||||
// Round up to page size
|
||||
return ((num_slots + slots_per_page - 1) / slots_per_page) * slots_per_page;
|
||||
@@ -3578,7 +3587,8 @@ size_t AutoHyperClockTable::CalcMaxUsableLength(
|
||||
|
||||
namespace {
|
||||
bool IsHeadNonempty(const AutoHyperClockTable::HandleImpl& h) {
|
||||
return !h.head_next_with_shift.LoadRelaxed().IsEnd();
|
||||
return !AutoHyperClockTable::HandleImpl::IsEnd(
|
||||
h.head_next_with_shift.LoadRelaxed());
|
||||
}
|
||||
bool IsEntryAtHome(const AutoHyperClockTable::HandleImpl& h, int shift,
|
||||
size_t home) {
|
||||
|
||||
Vendored
+63
-88
@@ -9,6 +9,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -17,10 +19,14 @@
|
||||
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/sharded_cache.h"
|
||||
#include "port/lang.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/mmap.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/atomic.h"
|
||||
#include "util/bit_fields.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/math.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -377,20 +383,25 @@ class BaseClockTable {
|
||||
int eviction_effort_cap;
|
||||
};
|
||||
|
||||
BaseClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
int eviction_effort_cap,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
BaseClockTable(CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed);
|
||||
const uint32_t* hash_seed)
|
||||
: metadata_charge_policy_(metadata_charge_policy),
|
||||
allocator_(allocator),
|
||||
eviction_callback_(*eviction_callback),
|
||||
hash_seed_(*hash_seed) {}
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* CreateStandalone(ClockHandleBasicData& proto,
|
||||
size_t capacity,
|
||||
uint32_t eec_and_scl,
|
||||
bool allow_uncharged);
|
||||
|
||||
template <class Table>
|
||||
Status Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle, Cache::Priority priority);
|
||||
typename Table::HandleImpl** handle, Cache::Priority priority,
|
||||
size_t capacity, uint32_t eec_and_scl);
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
@@ -400,18 +411,6 @@ class BaseClockTable {
|
||||
|
||||
size_t GetStandaloneUsage() const { return standalone_usage_.LoadRelaxed(); }
|
||||
|
||||
size_t GetCapacity() const { return capacity_.LoadRelaxed(); }
|
||||
|
||||
void SetCapacity(size_t capacity) { capacity_.StoreRelaxed(capacity); }
|
||||
|
||||
void SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
if (strict_capacity_limit) {
|
||||
eec_and_scl_.ApplyRelaxed(StrictCapacityLimit::SetTransform());
|
||||
} else {
|
||||
eec_and_scl_.ApplyRelaxed(StrictCapacityLimit::ClearTransform());
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GetHashSeed() const { return hash_seed_; }
|
||||
|
||||
uint64_t GetYieldCount() const { return yield_count_.LoadRelaxed(); }
|
||||
@@ -428,7 +427,6 @@ class BaseClockTable {
|
||||
|
||||
void TrackAndReleaseEvictedEntry(ClockHandle* h);
|
||||
|
||||
bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data) const;
|
||||
#ifndef NDEBUG
|
||||
// Acquire N references
|
||||
void TEST_RefN(ClockHandle& handle, size_t n);
|
||||
@@ -450,8 +448,9 @@ class BaseClockTable {
|
||||
// required, and the operation should fail if not possible.
|
||||
// NOTE: Otherwise, occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
Status ChargeUsageMaybeEvictStrict(size_t total_charge,
|
||||
Status ChargeUsageMaybeEvictStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
@@ -463,8 +462,9 @@ class BaseClockTable {
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge,
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
protected: // data
|
||||
@@ -497,25 +497,6 @@ class BaseClockTable {
|
||||
// Part of usage by standalone entries (not in table)
|
||||
AcqRelAtomic<size_t> standalone_usage_{};
|
||||
|
||||
// Maximum total charge of all elements stored in the table.
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
RelaxedAtomic<size_t> capacity_;
|
||||
|
||||
// Encodes eviction_effort_cap (bottom 31 bits) and strict_capacity_limit
|
||||
// (top bit). See HyperClockCacheOptions::eviction_effort_cap etc.
|
||||
struct EecAndScl : public BitFields<uint32_t, EecAndScl> {
|
||||
uint32_t GetEffectiveEvictionEffortCap() const {
|
||||
// Because setting strict_capacity_limit is supposed to imply infinite
|
||||
// cap on eviction effort, we can let the bit for strict_capacity_limit
|
||||
// in the upper-most bit position to used as part of the effective cap.
|
||||
return underlying;
|
||||
}
|
||||
};
|
||||
using EvictionEffortCap = UnsignedBitField<EecAndScl, 31, NoPrevBitField>;
|
||||
using StrictCapacityLimit = BoolBitField<EecAndScl, EvictionEffortCap>;
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
RelaxedBitFieldsAtomic<EecAndScl> eec_and_scl_;
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
const CacheMetadataChargePolicy metadata_charge_policy_;
|
||||
|
||||
@@ -570,7 +551,7 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
size_t estimated_value_size;
|
||||
};
|
||||
|
||||
FixedHyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
FixedHyperClockTable(size_t capacity,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
@@ -592,7 +573,8 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data,
|
||||
uint32_t eviction_effort_cap);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
@@ -775,7 +757,6 @@ 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
|
||||
@@ -795,63 +776,47 @@ 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>;
|
||||
|
||||
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" 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;
|
||||
|
||||
static NextWithShift Make(size_t next, int shift) {
|
||||
return NextWithShift{}.With<Next>(next).With<Shift>(
|
||||
static_cast<uint8_t>(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 MakeEnd(size_t next, int shift) {
|
||||
return Make(next, shift).With<EndFlag>(true).With<LockedFlag>(true);
|
||||
}
|
||||
};
|
||||
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;
|
||||
|
||||
// A marker for head_next_with_shift that indicates this HandleImpl is
|
||||
// heap allocated (standalone) rather than in the table.
|
||||
static constexpr NextWithShift kStandaloneMarker{UINT64_MAX};
|
||||
static constexpr uint64_t 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 NextWithShift kUnusedMarker{0};
|
||||
static constexpr uint64_t kUnusedMarker = 0;
|
||||
|
||||
// See above. The head pointer is logically independent of the rest of
|
||||
// the entry, including the chain next pointer.
|
||||
AcqRelBitFieldsAtomic<NextWithShift> head_next_with_shift{kUnusedMarker};
|
||||
AcqRelBitFieldsAtomic<NextWithShift> chain_next_with_shift{kUnusedMarker};
|
||||
AcqRelAtomic<uint64_t> head_next_with_shift{kUnusedMarker};
|
||||
AcqRelAtomic<uint64_t> chain_next_with_shift{kUnusedMarker};
|
||||
|
||||
// For supporting CreateStandalone and some fallback cases.
|
||||
inline bool IsStandalone() const {
|
||||
@@ -876,7 +841,7 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
size_t min_avg_value_size;
|
||||
};
|
||||
|
||||
AutoHyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
AutoHyperClockTable(size_t capacity,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
@@ -903,7 +868,8 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
// Runs the clock eviction algorithm trying to reclaim at least
|
||||
// requested_charge. Returns how much is evicted, which could be less
|
||||
// if it appears impossible to evict the requested amount without blocking.
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data,
|
||||
uint32_t eviction_effort_cap);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
@@ -1136,6 +1102,15 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
|
||||
private: // data
|
||||
Table table_;
|
||||
|
||||
// Maximum total charge of all elements stored in the table.
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
RelaxedAtomic<size_t> capacity_;
|
||||
|
||||
// Encodes eviction_effort_cap (bottom 31 bits) and strict_capacity_limit
|
||||
// (top bit). See HyperClockCacheOptions::eviction_effort_cap etc.
|
||||
// (Relaxed: eventual consistency/update is OK)
|
||||
RelaxedAtomic<uint32_t> eec_and_scl_;
|
||||
}; // class ClockCacheShard
|
||||
|
||||
template <class Table>
|
||||
|
||||
Vendored
+1
-1
@@ -1405,9 +1405,9 @@ TEST_P(BasicSecondaryCacheTest, SaveFailTest) {
|
||||
TestItem* item1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelperFail(), str1.length()));
|
||||
std::string str2 = rnd.RandomString(1020);
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
TestItem* item2 = new TestItem(str2.data(), str2.length());
|
||||
// k1 should be demoted to NVM
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
|
||||
ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelperFail(), str2.length()));
|
||||
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
|
||||
|
||||
|
||||
Vendored
+5
-5
@@ -33,7 +33,7 @@ const char* kTieredCacheName = "TieredCache";
|
||||
// proportionally across the primary/secondary caches.
|
||||
//
|
||||
// The primary block cache is initially sized to the sum of the primary cache
|
||||
// budget + the secondary cache budget, as follows -
|
||||
// budget + teh secondary cache budget, as follows -
|
||||
// |--------- Primary Cache Configured Capacity -----------|
|
||||
// |---Secondary Cache Budget----|----Primary Cache Budget-----|
|
||||
//
|
||||
@@ -51,7 +51,7 @@ const char* kTieredCacheName = "TieredCache";
|
||||
// placeholder is counted against the primary cache. To compensate and count
|
||||
// a portion of it against the secondary cache, the secondary cache Deflate()
|
||||
// method is called to shrink it. Since the Deflate() causes the secondary
|
||||
// actual usage to shrink, it is reflected here by releasing an equal amount
|
||||
// actual usage to shrink, it is refelcted here by releasing an equal amount
|
||||
// from the pri_cache_res_ reservation. The Deflate() in the secondary cache
|
||||
// can be, but is not required to be, implemented using its own cache
|
||||
// reservation manager.
|
||||
@@ -72,7 +72,7 @@ const char* kTieredCacheName = "TieredCache";
|
||||
// reservation is increased by an equal amount.
|
||||
//
|
||||
// Another way of implementing this would have been to simply split the user
|
||||
// reservation into primary and secondary components. However, this would
|
||||
// reservation into primary and seconary components. However, this would
|
||||
// require allocating a structure to track the associated secondary cache
|
||||
// reservation, which adds some complexity and overhead.
|
||||
//
|
||||
@@ -584,7 +584,7 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
size_t pri_capacity = target_->GetCapacity();
|
||||
size_t sec_capacity =
|
||||
static_cast<size_t>(pri_capacity * compressed_secondary_ratio);
|
||||
size_t old_sec_capacity = 0;
|
||||
size_t old_sec_capacity;
|
||||
Status s = secondary_cache_->GetCapacity(old_sec_capacity);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -621,7 +621,7 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
} else {
|
||||
// We're shrinking the ratio. Try to avoid unnecessary evictions -
|
||||
// 1. Lower the secondary cache capacity
|
||||
// 2. Decrease pri_cache_res_ reservation to reflect lower secondary
|
||||
// 2. Decrease pri_cache_res_ reservation to relect lower secondary
|
||||
// cache utilization (decrease in capacity - decrease in share of cache
|
||||
// reservations)
|
||||
// 3. Inflate the secondary cache to give it back the reduction in its
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ccache.exe cl.exe %*
|
||||
@@ -342,9 +342,6 @@ Status BuildTable(
|
||||
if (s.ok() && !empty) {
|
||||
if (flush_stats) {
|
||||
flush_stats->bytes_written_pre_comp = builder->PreCompressionSize();
|
||||
// Add worker CPU micros here. Caller needs to add CPU micros from
|
||||
// calling thread.
|
||||
flush_stats->cpu_micros += builder->GetWorkerCPUMicros();
|
||||
}
|
||||
uint64_t file_size = builder->FileSize();
|
||||
meta->fd.file_size = file_size;
|
||||
|
||||
-643
@@ -722,88 +722,6 @@ 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;
|
||||
@@ -1118,78 +1036,6 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_error_if_exists(options, 1);
|
||||
}
|
||||
|
||||
StartPhase("checkpoint_export_column_family");
|
||||
{
|
||||
static char cf_export_path[200];
|
||||
static char db_import_path[200];
|
||||
snprintf(cf_export_path, sizeof(cf_export_path),
|
||||
"%s/rocksdb_c_test-%d-cf_export", GetTempDir(), ((int)geteuid()));
|
||||
snprintf(db_import_path, sizeof(db_import_path),
|
||||
"%s/rocksdb_c_test-%d-db_import", GetTempDir(), ((int)geteuid()));
|
||||
|
||||
rocksdb_options_t* db_options = rocksdb_options_create();
|
||||
rocksdb_column_family_handle_t* cf_export =
|
||||
rocksdb_create_column_family(db, db_options, "cf_export", &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_put_cf(db, woptions, cf_export, "k1", 2, "v1", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put_cf(db, woptions, cf_export, "k2", 2, "v2", 2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_checkpoint_t* checkpoint =
|
||||
rocksdb_checkpoint_object_create(db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_export_import_files_metadata_t* export_metadata =
|
||||
rocksdb_checkpoint_export_column_family(checkpoint, cf_export,
|
||||
cf_export_path, &err);
|
||||
CheckNoError(err);
|
||||
const char* comparator_name =
|
||||
rocksdb_export_import_files_metadata_get_db_comparator_name(
|
||||
export_metadata);
|
||||
CheckEqual("leveldb.BytewiseComparator", comparator_name, 26);
|
||||
rocksdb_free((void*)comparator_name);
|
||||
rocksdb_checkpoint_object_destroy(checkpoint);
|
||||
checkpoint = NULL;
|
||||
rocksdb_drop_column_family(db, cf_export, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_column_family_handle_destroy(cf_export);
|
||||
rocksdb_options_set_create_if_missing(db_options, 1);
|
||||
rocksdb_options_set_error_if_exists(db_options, 1);
|
||||
rocksdb_t* db_import = rocksdb_open(db_options, db_import_path, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_import_column_family_options_t* import_options =
|
||||
rocksdb_import_column_family_options_create();
|
||||
rocksdb_column_family_handle_t* cf_import =
|
||||
rocksdb_create_column_family_with_import(db_import, db_options,
|
||||
"cf_import", import_options,
|
||||
export_metadata, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_import_column_family_options_destroy(import_options);
|
||||
rocksdb_export_import_files_metadata_destroy(export_metadata);
|
||||
size_t val_len;
|
||||
char* val =
|
||||
rocksdb_get_cf(db_import, roptions, cf_import, "k1", 2, &val_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual("v1", val, val_len);
|
||||
free(val);
|
||||
|
||||
val =
|
||||
rocksdb_get_cf(db_import, roptions, cf_import, "k2", 2, &val_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual("v2", val, val_len);
|
||||
free(val);
|
||||
|
||||
rocksdb_column_family_handle_destroy(cf_import);
|
||||
cf_import = NULL;
|
||||
rocksdb_close(db_import);
|
||||
rocksdb_destroy_db(db_options, db_import_path, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_options_destroy(db_options);
|
||||
db_options = NULL;
|
||||
}
|
||||
|
||||
StartPhase("compactall");
|
||||
rocksdb_compact_range(db, NULL, 0, NULL, 0);
|
||||
CheckGet(db, roptions, "foo", "hello");
|
||||
@@ -1337,70 +1183,6 @@ 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();
|
||||
@@ -1566,43 +1348,6 @@ 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);
|
||||
@@ -1688,53 +1433,6 @@ 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");
|
||||
@@ -2152,55 +1850,6 @@ 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",
|
||||
@@ -2277,74 +1926,6 @@ 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;
|
||||
|
||||
@@ -3816,17 +3397,6 @@ 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);
|
||||
@@ -4565,219 +4135,6 @@ 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);
|
||||
|
||||
rocksdb_compaction_service_options_override_destroy(override_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;
|
||||
|
||||
@@ -534,232 +534,6 @@ 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) {
|
||||
|
||||
@@ -281,9 +281,8 @@ Compaction::Compaction(
|
||||
std::vector<CompactionInputFiles> _inputs, int _output_level,
|
||||
uint64_t _target_file_size, uint64_t _max_compaction_bytes,
|
||||
uint32_t _output_path_id, CompressionType _compression,
|
||||
CompressionOptions _compression_opts,
|
||||
Temperature _output_temperature_override, uint32_t _max_subcompactions,
|
||||
std::vector<FileMetaData*> _grandparents,
|
||||
CompressionOptions _compression_opts, Temperature _output_temperature,
|
||||
uint32_t _max_subcompactions, std::vector<FileMetaData*> _grandparents,
|
||||
std::optional<SequenceNumber> _earliest_snapshot,
|
||||
const SnapshotChecker* _snapshot_checker,
|
||||
CompactionReason _compaction_reason, const std::string& _trim_ts,
|
||||
@@ -304,7 +303,7 @@ Compaction::Compaction(
|
||||
output_path_id_(_output_path_id),
|
||||
output_compression_(_compression),
|
||||
output_compression_opts_(_compression_opts),
|
||||
output_temperature_override_(_output_temperature_override),
|
||||
output_temperature_(_output_temperature),
|
||||
deletion_compaction_(_compaction_reason == CompactionReason::kFIFOTtl ||
|
||||
_compaction_reason ==
|
||||
CompactionReason::kFIFOMaxSize),
|
||||
@@ -1129,17 +1128,4 @@ void Compaction::FilterInputsForCompactionIterator() {
|
||||
}
|
||||
}
|
||||
|
||||
Temperature Compaction::GetOutputTemperature(bool is_proximal_level) const {
|
||||
if (output_temperature_override_ != Temperature::kUnknown) {
|
||||
return output_temperature_override_;
|
||||
}
|
||||
|
||||
if (is_last_level() && !is_proximal_level &&
|
||||
mutable_cf_options_.last_level_temperature != Temperature::kUnknown) {
|
||||
return mutable_cf_options_.last_level_temperature;
|
||||
}
|
||||
|
||||
return mutable_cf_options_.default_write_temperature;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -90,8 +90,7 @@ class Compaction {
|
||||
uint64_t target_file_size, uint64_t max_compaction_bytes,
|
||||
uint32_t output_path_id, CompressionType compression,
|
||||
CompressionOptions compression_opts,
|
||||
Temperature output_temperature_override,
|
||||
uint32_t max_subcompactions,
|
||||
Temperature output_temperature, uint32_t max_subcompactions,
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
std::optional<SequenceNumber> earliest_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
@@ -180,10 +179,6 @@ 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];
|
||||
}
|
||||
@@ -414,11 +409,7 @@ class Compaction {
|
||||
|
||||
uint64_t max_compaction_bytes() const { return max_compaction_bytes_; }
|
||||
|
||||
// Order of precedence for temperature:
|
||||
// 1. Override temp if not kUnknown
|
||||
// 2. Temperature of the last level files if applicable
|
||||
// 3. Default write temperature
|
||||
Temperature GetOutputTemperature(bool is_proximal_level = false) const;
|
||||
Temperature output_temperature() const { return output_temperature_; }
|
||||
|
||||
uint32_t max_subcompactions() const { return max_subcompactions_; }
|
||||
|
||||
@@ -550,7 +541,7 @@ class Compaction {
|
||||
const uint32_t output_path_id_;
|
||||
CompressionType output_compression_;
|
||||
CompressionOptions output_compression_opts_;
|
||||
Temperature output_temperature_override_;
|
||||
Temperature output_temperature_;
|
||||
// If true, then the compaction can be done by simply deleting input files.
|
||||
const bool deletion_compaction_;
|
||||
// should it split the output file using the compact cursor?
|
||||
|
||||
@@ -1120,15 +1120,17 @@ void CompactionIterator::NextFromInput() {
|
||||
}
|
||||
}
|
||||
|
||||
if (status_.ok()) {
|
||||
if (!Valid() && IsShuttingDown()) {
|
||||
status_ = Status::ShutdownInProgress();
|
||||
} else if (IsPausingManualCompaction()) {
|
||||
status_ = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
} else if (!input_.Valid() && input_.status().IsCorruption()) {
|
||||
// Propagate corruption status from memtable iterator
|
||||
status_ = input_.status();
|
||||
}
|
||||
if (!Valid() && IsShuttingDown()) {
|
||||
status_ = Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
if (IsPausingManualCompaction()) {
|
||||
status_ = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
|
||||
// Propagate corruption status from memtable itereator
|
||||
if (!input_.Valid() && input_.status().IsCorruption()) {
|
||||
status_ = input_.status();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,20 +183,10 @@ class CompactionIterator {
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
// @param must_count_input_entries Controls input entry counting accuracy vs
|
||||
// performance:
|
||||
// - If true: `NumInputEntryScanned()` always returns the exact count of
|
||||
// input keys
|
||||
// scanned. The iterator will use sequential `Next()` calls instead of
|
||||
// `Seek()` to maintain count accuracy as `Seek()` will not count the
|
||||
// skipped input entries, which is slower but guarantees correctness.
|
||||
// - If false: `NumInputEntryScanned()` returns the count only if no
|
||||
// `Seek()` operations
|
||||
// were performed on the input iterator. When compaction filters request
|
||||
// skipping ranges of keys or other optimizations trigger seek operations,
|
||||
// the count becomes unreliable. Always call `HasNumInputEntryScanned()`
|
||||
// first to verify if the count is accurate before using
|
||||
// `NumInputEntryScanned()`.
|
||||
// @param must_count_input_entries if true, `NumInputEntryScanned()` will
|
||||
// return the number of input keys scanned. If false, `NumInputEntryScanned()`
|
||||
// will return this number if no Seek was called on `input`. User should call
|
||||
// `HasNumInputEntryScanned()` first in this case.
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
@@ -265,21 +255,7 @@ class CompactionIterator {
|
||||
}
|
||||
const CompactionIterationStats& iter_stats() const { return iter_stats_; }
|
||||
bool HasNumInputEntryScanned() const { return input_.HasNumItered(); }
|
||||
|
||||
// This method should only be used when `HasNumInputEntryScanned()` returns
|
||||
// true, unless `must_count_input_entries=true` was specified during iterator
|
||||
// creation (which ensures the count is always accurate).
|
||||
uint64_t NumInputEntryScanned() const { return input_.NumItered(); }
|
||||
|
||||
// Returns true if the current valid key was already scanned/counted during
|
||||
// a lookahead operation in a previous iteration.
|
||||
//
|
||||
// REQUIRED: Valid() must be true
|
||||
bool IsCurrentKeyAlreadyScanned() const {
|
||||
assert(Valid());
|
||||
return at_next_ || merge_out_iter_.Valid();
|
||||
}
|
||||
|
||||
Status InputStatus() const { return input_.status(); }
|
||||
|
||||
bool IsDeleteRangeSentinelKey() const { return is_range_del_; }
|
||||
|
||||
+281
-897
File diff suppressed because it is too large.
Load diff
+12
-182
@@ -176,20 +176,9 @@ class CompactionJob {
|
||||
// and organizing seqno <-> time info. `known_single_subcompact` is non-null
|
||||
// if we already have a known single subcompaction, with optional key bounds
|
||||
// (currently for executing a remote compaction).
|
||||
//
|
||||
// @param compaction_progress Previously saved compaction progress
|
||||
// to resume from. If empty, compaction starts fresh from the
|
||||
// beginning.
|
||||
//
|
||||
// @param compaction_progress_writer Writer for persisting
|
||||
// subcompaction progress periodically during compaction
|
||||
// execution. If nullptr, progress tracking is disabled and compaction
|
||||
// cannot be resumed later.
|
||||
void Prepare(
|
||||
std::optional<std::pair<std::optional<Slice>, std::optional<Slice>>>
|
||||
known_single_subcompact,
|
||||
const CompactionProgress& compaction_progress = CompactionProgress{},
|
||||
log::Writer* compaction_progress_writer = nullptr);
|
||||
known_single_subcompact);
|
||||
|
||||
// REQUIRED mutex not held
|
||||
// Launch threads for each subcompaction and wait for them to finish. After
|
||||
@@ -207,8 +196,7 @@ class CompactionJob {
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
protected:
|
||||
void UpdateCompactionJobOutputStatsFromInternalStats(
|
||||
const Status& status,
|
||||
void UpdateCompactionJobOutputStats(
|
||||
const InternalStats::CompactionStatsFull& internal_stats) const;
|
||||
|
||||
void LogCompaction();
|
||||
@@ -254,10 +242,9 @@ class CompactionJob {
|
||||
// num_input_range_del are calculated successfully.
|
||||
//
|
||||
// This should be called only once for compactions (not per subcompaction)
|
||||
bool UpdateInternalStatsFromInputFiles(
|
||||
uint64_t* num_input_range_del = nullptr);
|
||||
bool BuildStatsFromInputFiles(uint64_t* num_input_range_del = nullptr);
|
||||
|
||||
void UpdateCompactionJobInputStatsFromInternalStats(
|
||||
void UpdateCompactionJobInputStats(
|
||||
const InternalStats::CompactionStatsFull& internal_stats,
|
||||
uint64_t num_input_range_del) const;
|
||||
|
||||
@@ -271,10 +258,6 @@ class CompactionJob {
|
||||
// consecutive groups such that each group has a similar size.
|
||||
void GenSubcompactionBoundaries();
|
||||
|
||||
void MaybeAssignCompactionProgressAndWriter(
|
||||
const CompactionProgress& compaction_progress,
|
||||
log::Writer* compaction_progress_writer);
|
||||
|
||||
// Get the number of planned subcompactions based on max_subcompactions and
|
||||
// extra reserved resources
|
||||
uint64_t GetSubcompactionsLimit();
|
||||
@@ -304,10 +287,7 @@ class CompactionJob {
|
||||
Status SyncOutputDirectories();
|
||||
Status VerifyOutputFiles();
|
||||
void SetOutputTableProperties();
|
||||
// Aggregates subcompaction output stats to internal stat, and aggregates
|
||||
// subcompaction's compaction job stats to the whole entire surrounding
|
||||
// compaction job stats.
|
||||
void AggregateSubcompactionOutputAndJobStats();
|
||||
void AggregateSubcompactionStats();
|
||||
Status VerifyCompactionRecordCounts(bool stats_built_from_input_table_prop,
|
||||
uint64_t num_input_range_del);
|
||||
void FinalizeCompactionRun(const Status& status,
|
||||
@@ -317,123 +297,15 @@ class CompactionJob {
|
||||
CompactionServiceJobStatus ProcessKeyValueCompactionWithCompactionService(
|
||||
SubcompactionState* sub_compact);
|
||||
|
||||
struct CompactionIOStatsSnapshot {
|
||||
PerfLevel prev_perf_level = PerfLevel::kEnableTime;
|
||||
uint64_t prev_write_nanos = 0;
|
||||
uint64_t prev_fsync_nanos = 0;
|
||||
uint64_t prev_range_sync_nanos = 0;
|
||||
uint64_t prev_prepare_write_nanos = 0;
|
||||
uint64_t prev_cpu_write_nanos = 0;
|
||||
uint64_t prev_cpu_read_nanos = 0;
|
||||
};
|
||||
|
||||
struct SubcompactionKeyBoundaries {
|
||||
const std::optional<const Slice> start;
|
||||
const std::optional<const Slice> end;
|
||||
|
||||
// Boundaries without timestamps for read options
|
||||
std::optional<Slice> start_without_ts;
|
||||
std::optional<Slice> end_without_ts;
|
||||
|
||||
// Timestamp management
|
||||
static constexpr char kMaxTs[] =
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
std::string max_ts;
|
||||
Slice ts_slice;
|
||||
|
||||
// Internal key boundaries
|
||||
IterKey start_ikey;
|
||||
IterKey end_ikey;
|
||||
Slice start_internal_key;
|
||||
Slice end_internal_key;
|
||||
|
||||
// User key boundaries
|
||||
Slice start_user_key;
|
||||
Slice end_user_key;
|
||||
|
||||
SubcompactionKeyBoundaries(std::optional<const Slice> start_boundary,
|
||||
std::optional<const Slice> end_boundary)
|
||||
: start(start_boundary), end(end_boundary) {}
|
||||
};
|
||||
|
||||
struct SubcompactionInternalIterators {
|
||||
std::unique_ptr<InternalIterator> raw_input;
|
||||
std::unique_ptr<InternalIterator> clip;
|
||||
std::unique_ptr<InternalIterator> blob_counter;
|
||||
std::unique_ptr<InternalIterator> trim_history_iter;
|
||||
};
|
||||
|
||||
struct BlobFileResources {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder;
|
||||
};
|
||||
|
||||
bool ShouldUseLocalCompaction(SubcompactionState* sub_compact);
|
||||
CompactionIOStatsSnapshot InitializeIOStats();
|
||||
Status SetupAndValidateCompactionFilter(
|
||||
SubcompactionState* sub_compact,
|
||||
const CompactionFilter* configured_compaction_filter,
|
||||
const CompactionFilter*& compaction_filter,
|
||||
std::unique_ptr<CompactionFilter>& compaction_filter_from_factory);
|
||||
void InitializeReadOptionsAndBoundaries(
|
||||
size_t ts_sz, ReadOptions& read_options,
|
||||
SubcompactionKeyBoundaries& boundaries);
|
||||
InternalIterator* CreateInputIterator(
|
||||
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
|
||||
SubcompactionInternalIterators& iterators,
|
||||
SubcompactionKeyBoundaries& boundaries, ReadOptions& read_options);
|
||||
void CreateBlobFileBuilder(SubcompactionState* sub_compact,
|
||||
ColumnFamilyData* cfd,
|
||||
BlobFileResources& blob_resources,
|
||||
const WriteOptions& write_options);
|
||||
std::unique_ptr<CompactionIterator> CreateCompactionIterator(
|
||||
SubcompactionState* sub_compact, ColumnFamilyData* cfd,
|
||||
InternalIterator* input_iter, const CompactionFilter* compaction_filter,
|
||||
MergeHelper& merge, BlobFileResources& blob_resources,
|
||||
const WriteOptions& write_options);
|
||||
std::pair<CompactionFileOpenFunc, CompactionFileCloseFunc> CreateFileHandlers(
|
||||
SubcompactionState* sub_compact, SubcompactionKeyBoundaries& boundaries);
|
||||
Status ProcessKeyValue(SubcompactionState* sub_compact, ColumnFamilyData* cfd,
|
||||
CompactionIterator* c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
uint64_t& prev_cpu_micros);
|
||||
void UpdateSubcompactionJobStatsIncrementally(
|
||||
CompactionIterator* c_iter, CompactionJobStats* compaction_job_stats,
|
||||
uint64_t cur_cpu_micros, uint64_t& prev_cpu_micros);
|
||||
void FinalizeSubcompactionJobStats(SubcompactionState* sub_compact,
|
||||
CompactionIterator* c_iter,
|
||||
uint64_t start_cpu_micros,
|
||||
uint64_t prev_cpu_micros,
|
||||
const CompactionIOStatsSnapshot& io_stats);
|
||||
Status FinalizeProcessKeyValueStatus(ColumnFamilyData* cfd,
|
||||
InternalIterator* input_iter,
|
||||
CompactionIterator* c_iter,
|
||||
Status status);
|
||||
Status CleanupCompactionFiles(SubcompactionState* sub_compact, Status status,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func);
|
||||
Status FinalizeBlobFiles(SubcompactionState* sub_compact,
|
||||
BlobFileBuilder* blob_file_builder, Status status);
|
||||
void FinalizeSubcompaction(SubcompactionState* sub_compact, Status status,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
CompactionIterator* c_iter,
|
||||
InternalIterator* input_iter,
|
||||
uint64_t start_cpu_micros,
|
||||
uint64_t prev_cpu_micros,
|
||||
const CompactionIOStatsSnapshot& io_stats);
|
||||
|
||||
// update the thread status for starting a compaction.
|
||||
void ReportStartedCompaction(Compaction* compaction);
|
||||
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status,
|
||||
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);
|
||||
Status FinishCompactionOutputFile(const Status& input_status,
|
||||
SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs,
|
||||
const Slice& next_table_min_key,
|
||||
const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key);
|
||||
Status InstallCompactionResults(bool* compaction_released);
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs);
|
||||
@@ -510,9 +382,6 @@ class CompactionJob {
|
||||
// Setting this requires DBMutex.
|
||||
uint64_t options_file_number_ = 0;
|
||||
|
||||
// Writer for persisting compaction progress during compaction
|
||||
log::Writer* compaction_progress_writer_ = nullptr;
|
||||
|
||||
// Get table file name in where it's outputting to, which should also be in
|
||||
// `output_directory_`.
|
||||
virtual std::string GetTableFileName(uint64_t file_number);
|
||||
@@ -520,43 +389,6 @@ class CompactionJob {
|
||||
// The Compaction Read and Write priorities are the same for different
|
||||
// scenarios, such as write stalled.
|
||||
Env::IOPriority GetRateLimiterPriority();
|
||||
|
||||
Status MaybeResumeSubcompactionProgressOnInputIterator(
|
||||
SubcompactionState* sub_compact, InternalIterator* input_iter);
|
||||
|
||||
Status ReadOutputFilesTableProperties(
|
||||
const autovector<FileMetaData>& temporary_output_file_allocation,
|
||||
const ReadOptions& read_options,
|
||||
std::vector<std::shared_ptr<const TableProperties>>&
|
||||
output_files_table_properties,
|
||||
bool is_proximal_level = false);
|
||||
|
||||
Status ReadTablePropertiesDirectly(
|
||||
const ImmutableOptions& ioptions, const MutableCFOptions& moptions,
|
||||
const FileMetaData* file_meta, const ReadOptions& read_options,
|
||||
std::shared_ptr<const TableProperties>* tp);
|
||||
|
||||
void RestoreCompactionOutputs(
|
||||
const ColumnFamilyData* cfd,
|
||||
const std::vector<std::shared_ptr<const TableProperties>>&
|
||||
output_files_table_properties,
|
||||
SubcompactionProgressPerLevel& subcompaction_progress_per_level,
|
||||
CompactionOutputs* outputs_to_restore);
|
||||
|
||||
bool ShouldUpdateSubcompactionProgress(
|
||||
const SubcompactionState* sub_compact, const CompactionIterator* c_iter,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key,
|
||||
const Slice& next_table_min_internal_key, const FileMetaData* meta) const;
|
||||
|
||||
void UpdateSubcompactionProgress(const CompactionIterator* c_iter,
|
||||
const Slice next_table_min_key,
|
||||
SubcompactionState* sub_compact);
|
||||
|
||||
Status PersistSubcompactionProgress(SubcompactionState* sub_compact);
|
||||
|
||||
void UpdateSubcompactionProgressPerLevel(
|
||||
SubcompactionState* sub_compact, bool is_proximal_level,
|
||||
SubcompactionProgress& subcompaction_progress);
|
||||
};
|
||||
|
||||
// CompactionServiceInput is used the pass compaction information between two
|
||||
@@ -706,9 +538,7 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
|
||||
// REQUIRED: mutex held
|
||||
// Like CompactionJob::Prepare()
|
||||
void Prepare(
|
||||
const CompactionProgress& compaction_progress = CompactionProgress{},
|
||||
log::Writer* compaction_progress_writer = nullptr);
|
||||
void Prepare();
|
||||
|
||||
// Run the compaction in current thread and return the result
|
||||
Status Run();
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/version_set.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "options/options_helper.h"
|
||||
@@ -211,12 +210,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_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false)),
|
||||
/*error_handler=*/nullptr, /*read_only=*/false)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
error_handler_(nullptr, db_options_, &mutex_),
|
||||
@@ -546,13 +545,13 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ASSERT_OK(s);
|
||||
db_options_.info_log = info_log;
|
||||
|
||||
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));
|
||||
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));
|
||||
compaction_job_stats_.Reset();
|
||||
|
||||
VersionEdit new_db;
|
||||
@@ -2410,458 +2409,6 @@ TEST_F(CompactionJobIOPriorityTest, GetRateLimiterPriority) {
|
||||
Env::IO_LOW, Env::IO_LOW);
|
||||
}
|
||||
|
||||
class ResumableCompactionJobTest : public CompactionJobTestBase {
|
||||
public:
|
||||
ResumableCompactionJobTest()
|
||||
: CompactionJobTestBase(
|
||||
test::PerThreadDBPath("allow_resumption_job_test"),
|
||||
BytewiseComparator(), [](uint64_t /*ts*/) { return ""; },
|
||||
/*test_io_priority=*/false, TableTypeForTest::kBlockBasedTable) {}
|
||||
|
||||
protected:
|
||||
static constexpr const char* kCancelBeforeThisKey = "cancel_before_this_key";
|
||||
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();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionOutputs::ShouldStopBefore::manual_decision",
|
||||
[this](void* p) {
|
||||
auto* pair = static_cast<std::pair<bool*, const Slice>*>(p);
|
||||
*(pair->first) = true;
|
||||
|
||||
// Cancel after outputting a specific key
|
||||
if (enable_cancel_) {
|
||||
ParsedInternalKey parsed_key;
|
||||
if (ParseInternalKey(pair->second, &parsed_key, true).ok()) {
|
||||
if (parsed_key.user_key == kCancelBeforeThisKey &&
|
||||
(cancel_before_seqno == kMaxSequenceNumber ||
|
||||
parsed_key.sequence == cancel_before_seqno)) {
|
||||
cancel_.store(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
if (env_->FileExists(progress_dir_).ok()) {
|
||||
std::vector<std::string> files;
|
||||
EXPECT_OK(env_->GetChildren(progress_dir_, &files));
|
||||
for (const auto& file : files) {
|
||||
if (file != "." && file != "..") {
|
||||
EXPECT_OK(env_->DeleteFile(progress_dir_ + "/" + file));
|
||||
}
|
||||
}
|
||||
EXPECT_OK(env_->DeleteDir(progress_dir_));
|
||||
}
|
||||
|
||||
CompactionJobTestBase::TearDown();
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
if (env_->FileExists(progress_dir_).ok()) {
|
||||
std::vector<std::string> files;
|
||||
EXPECT_OK(env_->GetChildren(progress_dir_, &files));
|
||||
for (const auto& file : files) {
|
||||
if (file != "." && file != "..") {
|
||||
EXPECT_OK(env_->DeleteFile(progress_dir_ + "/" + file));
|
||||
}
|
||||
}
|
||||
EXPECT_OK(env_->DeleteDir(progress_dir_));
|
||||
}
|
||||
|
||||
CompactionJobTestBase::NewDB();
|
||||
|
||||
progress_dir_ = test::PerThreadDBPath("compaction_progress");
|
||||
ASSERT_OK(env_->CreateDirIfMissing(progress_dir_));
|
||||
}
|
||||
|
||||
void EnableCompactionCancel() { enable_cancel_ = true; }
|
||||
|
||||
void DisableCompactionCancel() {
|
||||
enable_cancel_ = false;
|
||||
cancel_.store(false);
|
||||
}
|
||||
|
||||
std::unique_ptr<log::Writer> CreateCompactionProgressWriter(
|
||||
const std::string& compaction_progress_file) {
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
EXPECT_OK(fs_->NewWritableFile(compaction_progress_file, FileOptions(),
|
||||
&file, nullptr));
|
||||
auto file_writer = std::make_unique<WritableFileWriter>(
|
||||
std::move(file), compaction_progress_file, FileOptions());
|
||||
auto compaction_progress_writer =
|
||||
std::make_unique<log::Writer>(std::move(file_writer), 0, false);
|
||||
return compaction_progress_writer;
|
||||
}
|
||||
|
||||
Status RunCompactionWithProgressTracking(
|
||||
const CompactionProgress& compaction_progress,
|
||||
log::Writer* compaction_progress_writer,
|
||||
std::vector<SequenceNumber> snapshots = {},
|
||||
std::shared_ptr<Statistics> stats = nullptr) {
|
||||
mutex_.Lock();
|
||||
|
||||
auto cfd = versions_->GetColumnFamilySet()->GetDefault();
|
||||
auto files = cfd->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
db_options_.statistics = stats;
|
||||
db_options_.stats = db_options_.statistics.get();
|
||||
|
||||
std::vector<CompactionInputFiles> compaction_input_files;
|
||||
CompactionInputFiles level;
|
||||
level.level = 0;
|
||||
level.files = files;
|
||||
compaction_input_files.push_back(level);
|
||||
|
||||
Compaction compaction(
|
||||
cfd->current()->storage_info(), cfd->ioptions(),
|
||||
cfd->GetLatestMutableCFOptions(), mutable_db_options_,
|
||||
compaction_input_files, 1, mutable_cf_options_.target_file_size_base,
|
||||
mutable_cf_options_.max_compaction_bytes, 0, kNoCompression,
|
||||
cfd->GetLatestMutableCFOptions().compression_opts,
|
||||
Temperature::kUnknown, 0, {}, std::nullopt, nullptr,
|
||||
CompactionReason::kManualCompaction);
|
||||
compaction.FinalizeInputInfo(cfd->current());
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
JobContext job_context(1, false);
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
|
||||
std::move(snapshots));
|
||||
CompactionJobStats job_stats;
|
||||
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, mutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
|
||||
nullptr, stats.get(), &mutex_, &error_handler_, &job_context,
|
||||
table_cache_, &event_logger, false, false, dbname_, &job_stats,
|
||||
Env::Priority::USER, nullptr, cancel_, env_->GenerateUniqueId(),
|
||||
DBImpl::GenerateDbSessionId(nullptr), "");
|
||||
|
||||
compaction_job.Prepare(std::nullopt, compaction_progress,
|
||||
compaction_progress_writer);
|
||||
mutex_.Unlock();
|
||||
|
||||
compaction_job.Run().PermitUncheckedError();
|
||||
EXPECT_OK(compaction_job.io_status());
|
||||
|
||||
mutex_.Lock();
|
||||
|
||||
bool compaction_released = false;
|
||||
Status s = compaction_job.Install(&compaction_released);
|
||||
|
||||
mutex_.Unlock();
|
||||
if (!compaction_released) {
|
||||
compaction.ReleaseCompactionFiles(s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
SubcompactionProgress ReadAndParseProgress(
|
||||
const std::string& compaction_progress_file) {
|
||||
std::unique_ptr<FSSequentialFile> seq_file;
|
||||
EXPECT_OK(fs_->NewSequentialFile(compaction_progress_file, FileOptions(),
|
||||
&seq_file, nullptr));
|
||||
auto file_reader = std::make_unique<SequentialFileReader>(
|
||||
std::move(seq_file), compaction_progress_file, 0, nullptr);
|
||||
log::Reader reader(nullptr, std::move(file_reader), nullptr, true, 0);
|
||||
|
||||
SubcompactionProgressBuilder builder;
|
||||
std::string record;
|
||||
Slice slice;
|
||||
|
||||
while (reader.ReadRecord(&slice, &record)) {
|
||||
VersionEdit edit;
|
||||
if (!edit.DecodeFrom(slice).ok()) {
|
||||
continue;
|
||||
}
|
||||
builder.ProcessVersionEdit(edit);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(builder.HasAccumulatedSubcompactionProgress());
|
||||
|
||||
return builder.GetAccumulatedSubcompactionProgress();
|
||||
}
|
||||
|
||||
// Test utility function to verify that compaction progress was correctly
|
||||
// persisted to the progress file after compaction interruption.
|
||||
//
|
||||
// VERIFIES:
|
||||
// - Progress file exists and has expected size (empty if no progress
|
||||
// expected)
|
||||
// - Next internal key to compact matches expected user key with proper format
|
||||
// - Number of processed input records matches position in ordered input keys
|
||||
// - Number of processed output records equals number of processed input
|
||||
// records (by test design to simplify verification)
|
||||
// - Each output file contains exactly one user key (by test design to
|
||||
// simplify verification)
|
||||
void VerifyCompactionProgressPersisted(
|
||||
const std::string& compaction_progress_file,
|
||||
const std::string& next_user_key_to_compact,
|
||||
const std::vector<std::string>& ordered_intput_keys) {
|
||||
ASSERT_OK(env_->FileExists(compaction_progress_file));
|
||||
|
||||
uint64_t file_size;
|
||||
ASSERT_OK(env_->GetFileSize(compaction_progress_file, &file_size));
|
||||
|
||||
if (next_user_key_to_compact.empty()) {
|
||||
ASSERT_EQ(file_size, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& subcompaction_progress =
|
||||
ReadAndParseProgress(compaction_progress_file);
|
||||
|
||||
ASSERT_FALSE(subcompaction_progress.next_internal_key_to_compact.empty());
|
||||
ParsedInternalKey parsed_next_key;
|
||||
ASSERT_OK(
|
||||
ParseInternalKey(subcompaction_progress.next_internal_key_to_compact,
|
||||
&parsed_next_key, true /* log_err_key */));
|
||||
ASSERT_EQ(parsed_next_key.user_key, next_user_key_to_compact);
|
||||
ASSERT_EQ(parsed_next_key.sequence, kMaxSequenceNumber);
|
||||
ASSERT_EQ(parsed_next_key.type, kValueTypeForSeek);
|
||||
|
||||
auto it = std::find(ordered_intput_keys.begin(), ordered_intput_keys.end(),
|
||||
next_user_key_to_compact);
|
||||
ASSERT_TRUE(it != ordered_intput_keys.end());
|
||||
|
||||
auto next_key_index = std::distance(ordered_intput_keys.begin(), it);
|
||||
|
||||
ASSERT_EQ(subcompaction_progress.num_processed_input_records,
|
||||
next_key_index);
|
||||
|
||||
ASSERT_EQ(subcompaction_progress.output_level_progress
|
||||
.GetNumProcessedOutputRecords(),
|
||||
next_key_index);
|
||||
|
||||
ASSERT_EQ(
|
||||
subcompaction_progress.output_level_progress.GetOutputFiles().size(),
|
||||
|
||||
next_key_index);
|
||||
|
||||
for (size_t i = 0;
|
||||
i <
|
||||
subcompaction_progress.output_level_progress.GetOutputFiles().size();
|
||||
++i) {
|
||||
const auto& output_file =
|
||||
subcompaction_progress.output_level_progress.GetOutputFiles()[i];
|
||||
ASSERT_EQ(output_file.smallest.user_key().ToString(),
|
||||
output_file.largest.user_key().ToString());
|
||||
ASSERT_EQ(output_file.largest.user_key().ToString(),
|
||||
ordered_intput_keys[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void RunCancelAndResumeTest(
|
||||
const std::initializer_list<mock::KVPair>& input_file_1,
|
||||
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 cancelled_past_mid_point = false) {
|
||||
std::shared_ptr<Statistics> stats = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
auto file1 = mock::MakeMockFile(input_file_1);
|
||||
AddMockFile(file1);
|
||||
auto file2 = mock::MakeMockFile(input_file_2);
|
||||
AddMockFile(file2);
|
||||
SetLastSequence(last_sequence);
|
||||
|
||||
// First compaction (will be cancelled)
|
||||
std::string compaction_progress_file =
|
||||
CompactionProgressFileName(progress_dir_, 123);
|
||||
std::unique_ptr<log::Writer> compaction_progress_writer =
|
||||
CreateCompactionProgressWriter(compaction_progress_file);
|
||||
|
||||
ASSERT_OK(stats->Reset());
|
||||
EnableCompactionCancel();
|
||||
|
||||
Status status = RunCompactionWithProgressTracking(
|
||||
CompactionProgress{}, compaction_progress_writer.get(), snapshots,
|
||||
stats);
|
||||
|
||||
ASSERT_TRUE(status.IsManualCompactionPaused());
|
||||
DisableCompactionCancel();
|
||||
|
||||
HistogramData cancelled_compaction_stats;
|
||||
stats->histogramData(FILE_WRITE_COMPACTION_MICROS,
|
||||
&cancelled_compaction_stats);
|
||||
|
||||
VerifyCompactionProgressPersisted(compaction_progress_file,
|
||||
expected_next_key_to_compact,
|
||||
expected_input_keys);
|
||||
|
||||
// Resume compaction
|
||||
CompactionProgress compaction_progress;
|
||||
if (expected_next_key_to_compact != "") {
|
||||
compaction_progress.push_back(
|
||||
ReadAndParseProgress(compaction_progress_file));
|
||||
}
|
||||
|
||||
std::string compaction_progress_file_2 =
|
||||
CompactionProgressFileName(progress_dir_, 234);
|
||||
std::unique_ptr<log::Writer> compaction_progress_writer_2 =
|
||||
CreateCompactionProgressWriter(compaction_progress_file_2);
|
||||
|
||||
ASSERT_OK(stats->Reset());
|
||||
|
||||
status = RunCompactionWithProgressTracking(
|
||||
compaction_progress, compaction_progress_writer_2.get(),
|
||||
{} /* snapshots */, stats);
|
||||
|
||||
ASSERT_OK(status);
|
||||
|
||||
if (cancelled_past_mid_point) {
|
||||
HistogramData resumed_compaction_stats;
|
||||
stats->histogramData(FILE_WRITE_COMPACTION_MICROS,
|
||||
&resumed_compaction_stats);
|
||||
ASSERT_GT(cancelled_compaction_stats.count,
|
||||
resumed_compaction_stats.count);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, BasicProgressPersistence) {
|
||||
NewDB();
|
||||
|
||||
auto file1 = mock::MakeMockFile({
|
||||
{KeyStr("a", 1U, kTypeValue), "val1"},
|
||||
{KeyStr("b", 2U, kTypeValue), "val2"},
|
||||
});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile({
|
||||
{KeyStr("c", 3U, kTypeValue), "val3"},
|
||||
{KeyStr("d", 4U, kTypeValue), "val4"},
|
||||
});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(4U);
|
||||
|
||||
std::string compaction_progress_file =
|
||||
CompactionProgressFileName(progress_dir_, 123);
|
||||
|
||||
std::unique_ptr<log::Writer> compaction_progress_writer =
|
||||
CreateCompactionProgressWriter(compaction_progress_file);
|
||||
|
||||
Status status = RunCompactionWithProgressTracking(
|
||||
CompactionProgress(), compaction_progress_writer.get());
|
||||
|
||||
ASSERT_OK(status);
|
||||
|
||||
VerifyCompactionProgressPersisted(
|
||||
compaction_progress_file, "d" /* next_user_key_to_compact */,
|
||||
{"a", "b", "c", "d"} /* ordered_intput_keys */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, BasicProgressResume) {
|
||||
NewDB();
|
||||
|
||||
RunCancelAndResumeTest(
|
||||
{{KeyStr("a", 1U, kTypeValue), "val1"},
|
||||
{KeyStr("b", 2U, kTypeValue), "val2"}} /* input_file_1 */,
|
||||
{{KeyStr("bb", 3U, kTypeValue), "val3"},
|
||||
{KeyStr(kCancelBeforeThisKey, 4U, kTypeValue),
|
||||
"val4"}} /* input_file_2 */,
|
||||
4U /* last_sequence */, {} /* snapshots */,
|
||||
kCancelBeforeThisKey /* expected_next_key_to_compact */,
|
||||
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */,
|
||||
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), "val11"},
|
||||
{KeyStr("d", 3U, kTypeValue), "val2"}} /* input_file_2 */,
|
||||
3U /* last_sequence */, {1U} /* snapshots */,
|
||||
"" /* expected_next_key_to_compact */,
|
||||
{kCancelBeforeThisKey, kCancelBeforeThisKey,
|
||||
"d"} /* expected_input_keys */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeleteRange) {
|
||||
NewDB();
|
||||
|
||||
RunCancelAndResumeTest(
|
||||
{{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) {
|
||||
merge_op_ = MergeOperators::CreateStringAppendOperator();
|
||||
NewDB();
|
||||
|
||||
RunCancelAndResumeTest(
|
||||
{{KeyStr("a", 1U, kTypeValue), "val1"},
|
||||
{KeyStr("b", 2U, kTypeValue), "val2"}} /* input_file_1 */,
|
||||
{{KeyStr("bb", 3U, kTypeValue), "val3"},
|
||||
{KeyStr(kCancelBeforeThisKey, 4U, kTypeMerge),
|
||||
"val4"}} /* input_file_2 */,
|
||||
4U /* last_sequence */, {} /* snapshots */,
|
||||
"bb" /* expected_next_key_to_compact */,
|
||||
{"a", "b", "bb", kCancelBeforeThisKey} /* expected_input_keys */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSingleDelete) {
|
||||
NewDB();
|
||||
|
||||
RunCancelAndResumeTest(
|
||||
{{KeyStr("a", 1U, kTypeValue), "val1"},
|
||||
{KeyStr("b", 2U, kTypeValue), "val2"},
|
||||
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
|
||||
"val3"}} /* input_file_1 */,
|
||||
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeSingleDeletion), ""},
|
||||
{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, NoProgressResumeOnDeletionAtBottom) {
|
||||
NewDB();
|
||||
|
||||
RunCancelAndResumeTest(
|
||||
{{KeyStr("a", 1U, kTypeValue), "val1"},
|
||||
{KeyStr("b", 2U, kTypeValue), "val2"},
|
||||
{KeyStr(kCancelBeforeThisKey, 3U, kTypeValue),
|
||||
"val3"}} /* input_file_1 */,
|
||||
{{KeyStr(kCancelBeforeThisKey, 4U, kTypeDeletion), ""},
|
||||
{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 */);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -56,7 +56,6 @@ Status CompactionOutputs::Finish(
|
||||
stats_.bytes_written += current_bytes;
|
||||
stats_.bytes_written_pre_comp += builder_->PreCompressionSize();
|
||||
stats_.num_output_files = static_cast<int>(outputs_.size());
|
||||
worker_cpu_micros_ += builder_->GetWorkerCPUMicros();
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -278,11 +277,7 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
}
|
||||
|
||||
// reach the max 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()) {
|
||||
if (current_output_file_size_ >= compaction_->max_output_file_size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -363,8 +358,7 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
Status CompactionOutputs::AddToOutput(
|
||||
const CompactionIterator& c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key) {
|
||||
const CompactionFileCloseFunc& close_file_func) {
|
||||
Status s;
|
||||
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
|
||||
if (is_range_del && compaction_->bottommost_level()) {
|
||||
@@ -375,8 +369,7 @@ Status CompactionOutputs::AddToOutput(
|
||||
}
|
||||
const Slice& key = c_iter.key();
|
||||
if (ShouldStopBefore(c_iter) && HasBuilder()) {
|
||||
s = close_file_func(c_iter.InputStatus(), prev_iter_output_internal_key,
|
||||
key, &c_iter, *this);
|
||||
s = close_file_func(*this, c_iter.InputStatus(), key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class CompactionOutputs;
|
||||
using CompactionFileOpenFunc = std::function<Status(CompactionOutputs&)>;
|
||||
using CompactionFileCloseFunc =
|
||||
std::function<Status(const Status&, const ParsedInternalKey&, const Slice&,
|
||||
const CompactionIterator*, CompactionOutputs&)>;
|
||||
std::function<Status(CompactionOutputs&, const Status&, const Slice&)>;
|
||||
|
||||
// Files produced by subcompaction, most of the functions are used by
|
||||
// compaction_job Open/Close compaction file functions.
|
||||
@@ -59,8 +58,6 @@ class CompactionOutputs {
|
||||
precalculated_hash, is_proximal_level_);
|
||||
}
|
||||
|
||||
const std::vector<Output>& GetOutputs() const { return outputs_; }
|
||||
|
||||
// Set new table builder for the current output
|
||||
void NewBuilder(const TableBuilderOptions& tboptions);
|
||||
|
||||
@@ -171,10 +168,6 @@ class CompactionOutputs {
|
||||
|
||||
uint64_t NumEntries() const { return builder_->NumEntries(); }
|
||||
|
||||
uint64_t GetWorkerCPUMicros() const {
|
||||
return worker_cpu_micros_ + (builder_ ? builder_->GetWorkerCPUMicros() : 0);
|
||||
}
|
||||
|
||||
void ResetBuilder() {
|
||||
builder_.reset();
|
||||
current_output_file_size_ = 0;
|
||||
@@ -198,10 +191,6 @@ class CompactionOutputs {
|
||||
std::pair<SequenceNumber, SequenceNumber> keep_seqno_range,
|
||||
const Slice& next_table_min_key, const std::string& full_history_ts_low);
|
||||
|
||||
void SetNumOutputRecords(uint64_t num_output_records) {
|
||||
stats_.num_output_records = num_output_records;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class SubcompactionState;
|
||||
|
||||
@@ -261,8 +250,7 @@ class CompactionOutputs {
|
||||
// close and open new compaction output with the functions provided.
|
||||
Status AddToOutput(const CompactionIterator& c_iter,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key);
|
||||
const CompactionFileCloseFunc& close_file_func);
|
||||
|
||||
// Close the current output. `open_file_func` is needed for creating new file
|
||||
// for range-dels only output file.
|
||||
@@ -278,12 +266,9 @@ class CompactionOutputs {
|
||||
!range_del_agg->IsEmpty()) {
|
||||
status = open_file_func(*this);
|
||||
}
|
||||
|
||||
if (HasBuilder()) {
|
||||
const ParsedInternalKey empty_internal_key{};
|
||||
const Slice empty_key{};
|
||||
Status s = close_file_func(status, empty_internal_key, empty_key,
|
||||
nullptr /* c_iter */, *this);
|
||||
Status s = close_file_func(*this, status, empty_key);
|
||||
if (!s.ok() && status.ok()) {
|
||||
status = s;
|
||||
}
|
||||
@@ -311,9 +296,6 @@ class CompactionOutputs {
|
||||
uint64_t current_output_file_size_ = 0;
|
||||
SequenceNumber smallest_preferred_seqno_ = kMaxSequenceNumber;
|
||||
|
||||
// Sum of all the GetWorkerCPUMicros() for all the closed builders so far.
|
||||
uint64_t worker_cpu_micros_ = 0;
|
||||
|
||||
// all the compaction outputs so far
|
||||
std::vector<Output> outputs_;
|
||||
|
||||
|
||||
@@ -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, nullptr,
|
||||
hint_index, &hint_index, true,
|
||||
next_smallest);
|
||||
} while (inputs->size() > old_size);
|
||||
|
||||
@@ -375,13 +375,12 @@ Compaction* CompactionPicker::PickCompactionForCompactFiles(
|
||||
// without configurable `CompressionOptions`, which is inconsistent.
|
||||
compression_type = compact_options.compression;
|
||||
}
|
||||
|
||||
auto c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, mutable_db_options, input_files,
|
||||
output_level, compact_options.output_file_size_limit,
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id, compression_type,
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_options.output_temperature_override,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, earliest_snapshot, snapshot_checker,
|
||||
CompactionReason::kManualCompaction);
|
||||
@@ -465,8 +464,7 @@ 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,
|
||||
const FileMetaData* starting_l0_file) {
|
||||
int base_index, bool only_expand_towards_right) {
|
||||
assert(!inputs->empty());
|
||||
assert(output_level_inputs->empty());
|
||||
const int input_level = inputs->level;
|
||||
@@ -522,11 +520,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, true, starting_l0_file);
|
||||
nullptr);
|
||||
} else {
|
||||
vstorage->GetOverlappingInputs(input_level, &all_start, &all_limit,
|
||||
&expanded_inputs.files, base_index,
|
||||
nullptr, true, starting_l0_file);
|
||||
nullptr);
|
||||
}
|
||||
uint64_t expanded_inputs_size = TotalFileSize(expanded_inputs.files);
|
||||
if (!ExpandInputsToCleanCut(cf_name, vstorage, &expanded_inputs)) {
|
||||
@@ -681,7 +679,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(vstorage, mutable_cf_options, output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
Temperature::kUnknown, compact_range_options.max_subcompactions,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_range_options.max_subcompactions,
|
||||
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kManualCompaction,
|
||||
trim_ts, /* score */ -1,
|
||||
@@ -872,8 +871,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
GetCompressionType(vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
Temperature::kUnknown, compact_range_options.max_subcompactions,
|
||||
std::move(grandparents),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
|
||||
CompactionReason::kManualCompaction, trim_ts, /* score */ -1,
|
||||
/* l0_files_might_overlap */ true,
|
||||
@@ -1232,7 +1231,7 @@ void CompactionPicker::PickFilesMarkedForCompaction(
|
||||
|
||||
bool CompactionPicker::GetOverlappingL0Files(
|
||||
VersionStorageInfo* vstorage, CompactionInputFiles* start_level_inputs,
|
||||
int output_level, int* parent_index, const FileMetaData* starting_l0_file) {
|
||||
int output_level, int* parent_index) {
|
||||
// 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());
|
||||
@@ -1243,11 +1242,7 @@ bool CompactionPicker::GetOverlappingL0Files(
|
||||
// which will include the picked file.
|
||||
start_level_inputs->files.clear();
|
||||
vstorage->GetOverlappingInputs(0, &smallest, &largest,
|
||||
&(start_level_inputs->files),
|
||||
/*hint_index=*/-1,
|
||||
/*file_index=*/nullptr,
|
||||
/*expand_range=*/true,
|
||||
/*starting_l0_file=*/starting_l0_file);
|
||||
&(start_level_inputs->files));
|
||||
|
||||
// If we include more L0 files in the same compaction run it can
|
||||
// cause the 'smallest' and 'largest' key to get extended to a
|
||||
|
||||
@@ -203,16 +203,13 @@ 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,
|
||||
const FileMetaData* starting_l0_file = nullptr);
|
||||
bool only_expand_towards_right = false);
|
||||
|
||||
void GetGrandparents(VersionStorageInfo* vstorage,
|
||||
const CompactionInputFiles& inputs,
|
||||
@@ -225,12 +222,9 @@ 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,
|
||||
const FileMetaData* starting_l0_file = nullptr);
|
||||
int output_level, int* parent_index);
|
||||
|
||||
// Register this compaction in the set of running compactions
|
||||
void RegisterCompaction(Compaction* c);
|
||||
|
||||
@@ -124,7 +124,8 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
|
||||
std::move(inputs), 0, 0, 0, 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kFIFOTtl,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
@@ -193,7 +194,8 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
{comp_inputs}, 0, 16 * 1024 * 1024 /* output file size limit */,
|
||||
0 /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID */, mutable_cf_options.compression,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompactions */, {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
@@ -292,7 +294,8 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
/* target_file_size */ 0,
|
||||
/* max_compaction_bytes */ 0,
|
||||
/* output_path_id */ 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kFIFOMaxSize,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
@@ -387,14 +390,12 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
assert(compaction_target_temp == Temperature::kLastTemperature);
|
||||
compaction_target_temp = cur_target_temp;
|
||||
inputs[0].files.push_back(cur_file);
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with estimated newest key time %" PRIu64
|
||||
" and temperature %s for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(),
|
||||
est_newest_key_time,
|
||||
temperature_to_string[cur_file->temperature].c_str(),
|
||||
temperature_to_string[cur_target_temp].c_str());
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with estimated newest key time %" PRIu64 " for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(), est_newest_key_time,
|
||||
temperature_to_string[cur_target_temp].c_str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,7 +557,7 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
GetCompressionType(vstorage_, mutable_cf_options_, output_level_,
|
||||
vstorage_->base_level()),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level_),
|
||||
Temperature::kUnknown,
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, std::move(grandparents_),
|
||||
/* earliest_snapshot */ std::nullopt, /* snapshot_checker */ nullptr,
|
||||
compaction_reason_,
|
||||
|
||||
@@ -1178,7 +1178,7 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
@@ -1248,7 +1248,7 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
// Compaction picker picks older files first and picks one file at a time.
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
@@ -1316,7 +1316,7 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
// Compaction picker picks older files first and picks one file at a time.
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
@@ -1383,7 +1383,7 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
@@ -1464,35 +1464,12 @@ TEST_F(CompactionPickerTest, FIFOToHotAndWarm) {
|
||||
ASSERT_EQ(compaction->compaction_reason(),
|
||||
CompactionReason::kChangeTemperature);
|
||||
// Compaction picker picks older files first and picks one file at a time.
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kWarm);
|
||||
ASSERT_EQ(compaction->output_temperature(), Temperature::kWarm);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactFilesOutputTemperature) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
auto file_number = 66U;
|
||||
Add(0, file_number, "150", "200", 1000000000U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unordered_set<uint64_t> input{file_number};
|
||||
std::vector<CompactionInputFiles> input_files;
|
||||
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input, vstorage_.get(), CompactionOptions()));
|
||||
|
||||
auto compaction_options = CompactionOptions();
|
||||
compaction_options.output_temperature_override = Temperature::kCold;
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
level_compaction_picker.PickCompactionForCompactFiles(
|
||||
compaction_options, input_files, 1, vstorage_.get(),
|
||||
mutable_cf_options_, mutable_db_options_, /*output_path_id=*/0));
|
||||
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
ioptions_.compaction_pri = kMinOverlappingRatio;
|
||||
@@ -4716,53 +4693,6 @@ TEST_F(CompactionPickerTest, UniversalMaxReadAmpSmallDB) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, StandaloneRangeDeletionOnlyPicksOlderFiles) {
|
||||
NewVersionStorage(6, kCompactionStyleUniversal);
|
||||
|
||||
// Create L0 files with overlapping ranges
|
||||
// File 1: newest regular file (epoch 5), keys [100, 200]
|
||||
Add(0, 1U, "100", "200", 1U, 0, 100, 100, 0, false, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 5);
|
||||
|
||||
// File 2: standalone range deletion (epoch 4), keys [150, 250]
|
||||
// This file should be marked as having only range deletions
|
||||
Add(0, 2U, "150", "250", 1U, 0, 200, 200, 0, true, Temperature::kUnknown,
|
||||
kUnknownOldestAncesterTime, kUnknownNewestKeyTime, Slice(), Slice(), 4);
|
||||
|
||||
// Manually set file 2 as standalone range deletion
|
||||
FileMetaData* range_del_file = file_map_[2U].first;
|
||||
range_del_file->num_entries = 1;
|
||||
range_del_file->num_range_deletions = 1;
|
||||
ASSERT_TRUE(range_del_file->FileIsStandAloneRangeTombstone());
|
||||
|
||||
Add(4, 10U, "000", "400", 1U);
|
||||
Add(5, 20U, "000", "400", 100);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
ASSERT_TRUE(universal_compaction_picker.NeedsCompaction(vstorage_.get()));
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_,
|
||||
/*existing_snapshots=*/{}, /* snapshot_checker */ nullptr,
|
||||
vstorage_.get(), &log_buffer_));
|
||||
|
||||
ASSERT_NE(nullptr, compaction);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
// First input level should be L0 with only the standalone range del file
|
||||
// (file 2)
|
||||
ASSERT_EQ(0, compaction->level(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_TRUE(compaction->input(0, 0)->FileIsStandAloneRangeTombstone());
|
||||
|
||||
// Second input level should be L4 with file 10
|
||||
ASSERT_EQ(4, compaction->level(1));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(10U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -1093,7 +1093,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
output_level, 1, enable_compression),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_,
|
||||
output_level, enable_compression),
|
||||
Temperature::kUnknown,
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, grandparents,
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, compaction_reason,
|
||||
@@ -1441,7 +1441,7 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
true /* enable_compression */),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
Temperature::kUnknown,
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
@@ -1544,18 +1544,9 @@ 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,
|
||||
starting_l0_file)) {
|
||||
output_level, nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -1566,8 +1557,7 @@ 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, false,
|
||||
starting_l0_file)) {
|
||||
&parent_index, -1)) {
|
||||
return nullptr;
|
||||
}
|
||||
inputs.push_back(start_level_inputs);
|
||||
@@ -1604,7 +1594,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
/* max_grandparent_overlap_bytes */ GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(vstorage_, mutable_cf_options_, output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
|
||||
Temperature::kUnknown,
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, grandparents, earliest_snapshot_,
|
||||
snapshot_checker_, CompactionReason::kFilesMarkedForCompaction,
|
||||
/* trim_ts */ "", score_,
|
||||
@@ -1700,7 +1690,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
true /* enable_compression */),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
Temperature::kUnknown,
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, compaction_reason,
|
||||
|
||||
@@ -326,9 +326,7 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
compaction_input_(compaction_service_input),
|
||||
compaction_result_(compaction_service_result) {}
|
||||
|
||||
void CompactionServiceCompactionJob::Prepare(
|
||||
const CompactionProgress& compaction_progress,
|
||||
log::Writer* compaction_progress_writer) {
|
||||
void CompactionServiceCompactionJob::Prepare() {
|
||||
std::optional<Slice> begin;
|
||||
if (compaction_input_.has_begin) {
|
||||
begin = compaction_input_.begin;
|
||||
@@ -337,8 +335,7 @@ void CompactionServiceCompactionJob::Prepare(
|
||||
if (compaction_input_.has_end) {
|
||||
end = compaction_input_.end;
|
||||
}
|
||||
CompactionJob::Prepare(std::make_pair(begin, end), compaction_progress,
|
||||
compaction_progress_writer);
|
||||
CompactionJob::Prepare(std::make_pair(begin, end));
|
||||
}
|
||||
|
||||
Status CompactionServiceCompactionJob::Run() {
|
||||
@@ -411,7 +408,7 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
// 2. Update job-level output stats with the aggregated internal_stats_
|
||||
// Please note that input stats will be updated by primary host when all
|
||||
// subcompactions are finished
|
||||
UpdateCompactionJobOutputStatsFromInternalStats(status, internal_stats_);
|
||||
UpdateCompactionJobOutputStats(internal_stats_);
|
||||
// and set fields that are not propagated as part of the update
|
||||
compaction_result_->stats.is_manual_compaction = c->is_manual_compaction();
|
||||
compaction_result_->stats.is_full_compaction = c->is_full_compaction();
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "file/file_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/utilities/options_util.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
@@ -17,17 +16,17 @@ class MyTestCompactionService : public CompactionService {
|
||||
MyTestCompactionService(
|
||||
std::string db_path, Options& options,
|
||||
std::shared_ptr<Statistics>& statistics,
|
||||
std::vector<std::shared_ptr<EventListener>> listeners,
|
||||
std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
|
||||
table_properties_collector_factories)
|
||||
: db_path_(std::move(db_path)),
|
||||
statistics_(statistics),
|
||||
options_(options),
|
||||
statistics_(statistics),
|
||||
start_info_("na", "na", "na", 0, "na", 0, Env::TOTAL,
|
||||
CompactionReason::kUnknown, false, false, false, -1, -1),
|
||||
wait_info_("na", "na", "na", 0, "na", 0, Env::TOTAL,
|
||||
CompactionReason::kUnknown, false, false, false, -1, -1),
|
||||
listeners_(std::move(listeners)),
|
||||
listeners_(listeners),
|
||||
table_properties_collector_factories_(
|
||||
std::move(table_properties_collector_factories)) {}
|
||||
|
||||
@@ -73,31 +72,6 @@ class MyTestCompactionService : public CompactionService {
|
||||
if (is_override_wait_status_) {
|
||||
return override_wait_status_;
|
||||
}
|
||||
|
||||
CompactionServiceOptionsOverride options_override = GetOptionsOverride();
|
||||
|
||||
OpenAndCompactOptions options;
|
||||
options.canceled = &canceled_;
|
||||
|
||||
Status s =
|
||||
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
|
||||
compaction_input, result, options_override);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (is_override_wait_result_) {
|
||||
*result = override_wait_result_;
|
||||
}
|
||||
result_ = *result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
} else {
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
}
|
||||
|
||||
CompactionServiceOptionsOverride GetOptionsOverride() {
|
||||
CompactionServiceOptionsOverride options_override;
|
||||
options_override.env = options_.env;
|
||||
options_override.file_checksum_gen_factory =
|
||||
@@ -120,7 +94,26 @@ class MyTestCompactionService : public CompactionService {
|
||||
options_override.table_properties_collector_factories =
|
||||
table_properties_collector_factories_;
|
||||
}
|
||||
return options_override;
|
||||
|
||||
OpenAndCompactOptions options;
|
||||
options.canceled = &canceled_;
|
||||
|
||||
Status s =
|
||||
DB::OpenAndCompact(options, db_path_, db_path_ + "/" + scheduled_job_id,
|
||||
compaction_input, result, options_override);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (is_override_wait_result_) {
|
||||
*result = override_wait_result_;
|
||||
}
|
||||
result_ = *result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
} else {
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
}
|
||||
|
||||
void CancelAwaitingJobs() override { canceled_ = true; }
|
||||
@@ -167,21 +160,14 @@ class MyTestCompactionService : public CompactionService {
|
||||
return final_updated_status_.load();
|
||||
}
|
||||
|
||||
protected:
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
const std::string db_path_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
std::atomic_int compaction_num_{0};
|
||||
std::map<std::string, std::string> jobs_;
|
||||
std::map<std::string, CompactionServiceJobInfo> infos_;
|
||||
std::string result_;
|
||||
|
||||
std::string GetOutputPath(const std::string& scheduled_job_id) {
|
||||
return db_path_ + "/" + scheduled_job_id;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic_int compaction_num_{0};
|
||||
const std::string db_path_;
|
||||
Options options_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
CompactionServiceJobInfo start_info_;
|
||||
CompactionServiceJobInfo wait_info_;
|
||||
bool is_override_start_status_ = false;
|
||||
@@ -191,6 +177,7 @@ class MyTestCompactionService : public CompactionService {
|
||||
CompactionServiceJobStatus override_wait_status_ =
|
||||
CompactionServiceJobStatus::kFailure;
|
||||
bool is_override_wait_result_ = false;
|
||||
std::string result_;
|
||||
std::string override_wait_result_;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_;
|
||||
std::vector<std::shared_ptr<TablePropertiesCollectorFactory>>
|
||||
@@ -476,6 +463,8 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
|
||||
TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
size_t num_files_after_filtered = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -483,97 +472,83 @@ TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
|
||||
[&](void* arg) {
|
||||
num_files_after_filtered = *static_cast<size_t*>(arg);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (auto compaction_style : {CompactionStyle::kCompactionStyleLevel,
|
||||
CompactionStyle::kCompactionStyleUniversal}) {
|
||||
SCOPED_TRACE("Style: " + std::to_string(compaction_style));
|
||||
options.compaction_style = compaction_style;
|
||||
ReopenWithCompactionService(&options);
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
// Writes first version of data in range partitioned files.
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = dbname_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
|
||||
num_files_after_filtered = 0;
|
||||
|
||||
std::vector<std::string> files;
|
||||
{
|
||||
// Writes first version of data in range partitioned files.
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file1 = dbname_ + "file1.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file1));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a1"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b1"));
|
||||
ExternalSstFileInfo file1_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file1_info));
|
||||
files.push_back(std::move(file1));
|
||||
|
||||
std::string file2 = dbname_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x1"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y1"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("b"), "b1");
|
||||
ASSERT_EQ(Get("x"), "x1");
|
||||
ASSERT_EQ(Get("y"), "y1");
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
{
|
||||
// Atomically delete old version of data with one range delete file.
|
||||
// And a new batch of range partitioned files with new version of data.
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = dbname_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
|
||||
std::string file3 = dbname_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a2"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b2"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
|
||||
std::string file4 = dbname_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x2"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y2"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
}
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_OK(db_->WaitForCompact(WaitForCompactOptions()));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
|
||||
if (compaction_style == kCompactionStyleUniversal) {
|
||||
ASSERT_EQ(num_files_after_filtered, 1);
|
||||
} else {
|
||||
// Not filtered
|
||||
ASSERT_EQ(num_files_after_filtered, 3);
|
||||
}
|
||||
|
||||
Close();
|
||||
std::string file2 = dbname_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x1"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y1"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
}
|
||||
|
||||
IngestExternalFileOptions ifo;
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_EQ(Get("a"), "a1");
|
||||
ASSERT_EQ(Get("b"), "b1");
|
||||
ASSERT_EQ(Get("x"), "x1");
|
||||
ASSERT_EQ(Get("y"), "y1");
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(6));
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
{
|
||||
// Atomically delete old version of data with one range delete file.
|
||||
// And a new batch of range partitioned files with new version of data.
|
||||
files.clear();
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::string file2 = dbname_ + "file2.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file2));
|
||||
ASSERT_OK(sst_file_writer.DeleteRange("a", "z"));
|
||||
ExternalSstFileInfo file2_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file2_info));
|
||||
files.push_back(std::move(file2));
|
||||
|
||||
std::string file3 = dbname_ + "file3.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file3));
|
||||
ASSERT_OK(sst_file_writer.Put("a", "a2"));
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b2"));
|
||||
ExternalSstFileInfo file3_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file3_info));
|
||||
files.push_back(std::move(file3));
|
||||
|
||||
std::string file4 = dbname_ + "file4.sst";
|
||||
ASSERT_OK(sst_file_writer.Open(file4));
|
||||
ASSERT_OK(sst_file_writer.Put("x", "x2"));
|
||||
ASSERT_OK(sst_file_writer.Put("y", "y2"));
|
||||
ExternalSstFileInfo file4_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&file4_info));
|
||||
files.push_back(std::move(file4));
|
||||
}
|
||||
|
||||
ASSERT_OK(db_->IngestExternalFile(files, ifo));
|
||||
ASSERT_OK(db_->WaitForCompact(WaitForCompactOptions()));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
|
||||
ASSERT_EQ(num_files_after_filtered, 1);
|
||||
|
||||
Close();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
@@ -1047,7 +1022,6 @@ 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();
|
||||
|
||||
@@ -1102,87 +1076,6 @@ 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;
|
||||
@@ -2100,338 +1993,6 @@ TEST_F(CompactionServiceTest, TablePropertiesCollector) {
|
||||
ASSERT_TRUE(has_user_property);
|
||||
}
|
||||
|
||||
class ResumableCompactionService : public MyTestCompactionService {
|
||||
public:
|
||||
enum class TestScenario {
|
||||
// Test scenario 1: Two-phase compaction with resumption
|
||||
// - Phase 1: Cancel the compaction running with resumption enabled (saves
|
||||
// progress)
|
||||
// - Phase 2: Resume from saved progress and complete
|
||||
// Validates: Resumption reduces redundant work
|
||||
kCancelThenResume,
|
||||
|
||||
// Test scenario 2: Two-phase compaction without resumption
|
||||
// - Phase 1: Cancel the compaction running with resumption enabled (saves
|
||||
// progress)
|
||||
// - Phase 2: Start fresh without resumption (ignores saved progress) and
|
||||
// complete
|
||||
// Validates: Disabling resumption causes full reprocessing
|
||||
kCancelThenFreshStart,
|
||||
|
||||
// Test scenario 3: Three-phase compaction toggling resumption on/off/on
|
||||
// - Phase 1: Cancel the compaction running with resumption enabled (saves
|
||||
// progress)
|
||||
// - Phase 2: Start fresh wtihout resumption (ignores saved progress) and
|
||||
// cancel agains
|
||||
// - Phase 3: Resume with resumption support (loads Phase 1's progress) and
|
||||
// complete
|
||||
// Validates: Resumption state can be toggled;
|
||||
kMultipleCancelToggleResumption
|
||||
};
|
||||
|
||||
ResumableCompactionService(const std::string& db_path, Options& options,
|
||||
std::shared_ptr<Statistics> statistics,
|
||||
TestScenario scenario)
|
||||
: MyTestCompactionService(db_path, options, statistics,
|
||||
{} /* listeners */,
|
||||
{} /* table_properties_collector_factories */),
|
||||
scenario_(scenario) {}
|
||||
|
||||
CompactionServiceJobStatus Wait(const std::string& scheduled_job_id,
|
||||
std::string* result) override {
|
||||
std::string compaction_input = ExtractCompactionInput(scheduled_job_id);
|
||||
EXPECT_FALSE(compaction_input.empty());
|
||||
|
||||
OpenAndCompactOptions open_and_compaction_options;
|
||||
auto override_options = GetOptionsOverride();
|
||||
|
||||
// Force creation of one key per output file for test simplicity.
|
||||
// ASSUMPTION: This makes stats.count directly proportional to keys
|
||||
// processed.
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionOutputs::ShouldStopBefore::manual_decision", [](void* p) {
|
||||
auto* pair = static_cast<std::pair<bool*, const Slice>*>(p);
|
||||
*(pair->first) = true;
|
||||
});
|
||||
// Simulate cancelled compaction by overriding status at completion. So
|
||||
// compaction processes all keys before this point to make stats.count
|
||||
// comparison straightforward.
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
|
||||
auto s = static_cast<Status*>(status);
|
||||
*s = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Phase 1: Run compaction with resumption enabled and cancel it
|
||||
// - Processes all input keys
|
||||
// - Creates output files and saves progress
|
||||
// - Status overridden to "paused"
|
||||
open_and_compaction_options.allow_resumption = true;
|
||||
auto phase1_stats =
|
||||
RunCancelledCompaction(open_and_compaction_options, scheduled_job_id,
|
||||
compaction_input, override_options);
|
||||
|
||||
HistogramData phase2_stats;
|
||||
|
||||
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
|
||||
// Phase 2: Run compaction WITHOUT resumption (fresh start) and cancel it
|
||||
// - Delete all files left behind Phase 1 before calling OpenAndCompact()
|
||||
// - Processes all input keys again from scratch
|
||||
// - Creates output files but does NOT save progress
|
||||
// - Status overridden to "paused"
|
||||
open_and_compaction_options.allow_resumption = false;
|
||||
|
||||
// Clean up output folder for fresh start
|
||||
std::string output_dir = GetOutputPath(scheduled_job_id);
|
||||
Status cleanup_status = DestroyDir(override_options.env, output_dir);
|
||||
EXPECT_TRUE(cleanup_status.ok());
|
||||
EXPECT_OK(override_options.env->CreateDir(output_dir));
|
||||
|
||||
phase2_stats =
|
||||
RunCancelledCompaction(open_and_compaction_options, scheduled_job_id,
|
||||
compaction_input, override_options);
|
||||
|
||||
// Validation: Phase 2 starts from scratch, so it processes the same
|
||||
// input keys as Phase 1.
|
||||
// ASSUMPTION: With fixed input (10 keys) and deterministic cancellation
|
||||
// (after processing), both phases create the same number of output files.
|
||||
EXPECT_EQ(phase2_stats.count, phase1_stats.count);
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->ClearCallBack(
|
||||
"DBImplSecondary::CompactWithoutInstallation::End");
|
||||
|
||||
// Final phase: Run compaction to completion (no cancellation)
|
||||
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
|
||||
// Attempt to resume but it ends up starting fresh
|
||||
open_and_compaction_options.allow_resumption = true;
|
||||
} else if (scenario_ == TestScenario::kCancelThenResume) {
|
||||
// Resume from Phase 1's saved progress
|
||||
open_and_compaction_options.allow_resumption = true;
|
||||
} else { // kCancelThenFreshStart
|
||||
// Start fresh without resumption
|
||||
open_and_compaction_options.allow_resumption = false;
|
||||
|
||||
// Clean up output folder for fresh start
|
||||
std::string output_dir = GetOutputPath(scheduled_job_id);
|
||||
Status cleanup_status = DestroyDir(override_options.env, output_dir);
|
||||
EXPECT_TRUE(cleanup_status.ok());
|
||||
EXPECT_OK(override_options.env->CreateDir(output_dir));
|
||||
}
|
||||
|
||||
auto final_phase_stats =
|
||||
RunCompaction(open_and_compaction_options, scheduled_job_id,
|
||||
compaction_input, override_options, result);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// Validate statistics based on scenario
|
||||
if (scenario_ == TestScenario::kMultipleCancelToggleResumption) {
|
||||
// ASSUMPTION: Phase 1 processes all keys before cancellation
|
||||
EXPECT_GT(phase1_stats.count, 0);
|
||||
|
||||
// ASSUMPTION: Phase 2 runs with allow_resumption=false and an empty
|
||||
// folder. Phase 2 then creates its own output files (but doesn't save
|
||||
// progress). When Phase 3 starts with allow_resumption=true, it finds no
|
||||
// progress file exists, so it cannot resume and must start from scratch,
|
||||
// processing all input keys again.
|
||||
// Result: Phase 3 does the same amount of work as Phase 1.
|
||||
EXPECT_EQ(final_phase_stats.count, phase1_stats.count);
|
||||
|
||||
} else if (scenario_ == TestScenario::kCancelThenResume) {
|
||||
// ASSUMPTION: Phase 1 processes all keys before cancellation
|
||||
EXPECT_GT(phase1_stats.count, 0);
|
||||
|
||||
// ASSUMPTION: Phase 1 processes all keys and saves progress before
|
||||
// cancellation. Final phase resumes from Phase 1's saved progress.
|
||||
// Since Phase 1 completed all processing before being cancelled, the
|
||||
// final phase should do less work than Phase 1.
|
||||
EXPECT_LT(final_phase_stats.count, phase1_stats.count);
|
||||
|
||||
} else { // kCancelThenFreshStart
|
||||
// ASSUMPTION: Phase 1 processes all keys before cancellation
|
||||
EXPECT_GT(phase1_stats.count, 0);
|
||||
|
||||
// ASSUMPTION: Final phase starts fresh without resumption, so it
|
||||
// processes all input keys again and creates the same number of files
|
||||
EXPECT_EQ(final_phase_stats.count, phase1_stats.count);
|
||||
}
|
||||
|
||||
StoreResult(*result);
|
||||
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string ExtractCompactionInput(const std::string& scheduled_job_id) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
auto job_index = jobs_.find(scheduled_job_id);
|
||||
if (job_index == jobs_.end()) {
|
||||
return "";
|
||||
}
|
||||
std::string compaction_input = std::move(job_index->second);
|
||||
jobs_.erase(job_index);
|
||||
|
||||
auto info_index = infos_.find(scheduled_job_id);
|
||||
if (info_index == infos_.end()) {
|
||||
return "";
|
||||
}
|
||||
infos_.erase(info_index);
|
||||
|
||||
return compaction_input;
|
||||
}
|
||||
|
||||
HistogramData RunCancelledCompaction(
|
||||
const OpenAndCompactOptions& options, const std::string& scheduled_job_id,
|
||||
const std::string& compaction_input,
|
||||
const CompactionServiceOptionsOverride& override_options) {
|
||||
std::string temp_result;
|
||||
EXPECT_OK(statistics_->Reset());
|
||||
|
||||
Status s =
|
||||
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
|
||||
compaction_input, &temp_result, override_options);
|
||||
|
||||
EXPECT_TRUE(s.IsManualCompactionPaused());
|
||||
|
||||
HistogramData stats;
|
||||
statistics_->histogramData(FILE_WRITE_COMPACTION_MICROS, &stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
HistogramData RunCompaction(
|
||||
const OpenAndCompactOptions& options, const std::string& scheduled_job_id,
|
||||
const std::string& compaction_input,
|
||||
const CompactionServiceOptionsOverride& override_options,
|
||||
std::string* result) {
|
||||
EXPECT_OK(statistics_->Reset());
|
||||
|
||||
Status s =
|
||||
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
|
||||
compaction_input, result, override_options);
|
||||
|
||||
EXPECT_TRUE(s.ok());
|
||||
|
||||
HistogramData stats;
|
||||
statistics_->histogramData(FILE_WRITE_COMPACTION_MICROS, &stats);
|
||||
return stats;
|
||||
}
|
||||
|
||||
void StoreResult(const std::string& result) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
result_ = result;
|
||||
}
|
||||
|
||||
TestScenario scenario_;
|
||||
};
|
||||
|
||||
class ResumableCompactionServiceTest : public CompactionServiceTest {
|
||||
public:
|
||||
explicit ResumableCompactionServiceTest() : CompactionServiceTest() {}
|
||||
|
||||
void RunCompactionCancelTest(
|
||||
ResumableCompactionService::TestScenario scenario) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
std::shared_ptr<Statistics> statistics = CreateDBStatistics();
|
||||
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.verify_compression = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
auto resume_cs = std::make_shared<ResumableCompactionService>(
|
||||
dbname_, options, statistics, scenario);
|
||||
options.compaction_service = resume_cs;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
GenerateTestData();
|
||||
|
||||
ASSERT_OK(statistics->Reset());
|
||||
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
Status s = db_->CompactRange(cro, nullptr, nullptr);
|
||||
ASSERT_OK(s);
|
||||
|
||||
VerifyTestData();
|
||||
|
||||
s = db_->VerifyChecksum();
|
||||
ASSERT_OK(s);
|
||||
|
||||
s = db_->VerifyFileChecksums(ReadOptions());
|
||||
ASSERT_OK(s);
|
||||
|
||||
CompactionServiceResult result;
|
||||
resume_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
ASSERT_GT(result.output_files.size(), 0);
|
||||
|
||||
uint64_t resumed_bytes =
|
||||
statistics->getTickerCount(REMOTE_COMPACT_RESUMED_BYTES);
|
||||
if (scenario ==
|
||||
ResumableCompactionService::TestScenario::kCancelThenResume) {
|
||||
// When resuming compaction, some bytes should be resumed from previous
|
||||
// progress
|
||||
ASSERT_GT(resumed_bytes, 0);
|
||||
} else if (scenario == ResumableCompactionService::TestScenario::
|
||||
kCancelThenFreshStart) {
|
||||
// When starting fresh (ignoring existing progress), no bytes should be
|
||||
// resumed
|
||||
ASSERT_EQ(resumed_bytes, 0);
|
||||
} else { // kMultipleCancelToggleResumption
|
||||
// Phase 2 ran without resumption (fresh start), so Phase 3 has no
|
||||
// progress to resume from. It processes all keys again from scratch.
|
||||
ASSERT_EQ(resumed_bytes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateTestData() {
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), "value"));
|
||||
ASSERT_OK(Flush());
|
||||
if (i % 2 == 0) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyTestData() {
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
if (i % 2 == 0) {
|
||||
ASSERT_EQ("NOT_FOUND", Get((Key(i))));
|
||||
} else {
|
||||
ASSERT_EQ("value", Get((Key(i))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int kNumKeys = 10;
|
||||
};
|
||||
|
||||
TEST_F(ResumableCompactionServiceTest, CompactionCancelThenResume) {
|
||||
RunCompactionCancelTest(
|
||||
ResumableCompactionService::TestScenario::kCancelThenResume);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionServiceTest, CompactionCancelThenFreshStart) {
|
||||
RunCompactionCancelTest(
|
||||
ResumableCompactionService::TestScenario::kCancelThenFreshStart);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionServiceTest,
|
||||
CompactionMultipleCancelToggleResumption) {
|
||||
RunCompactionCancelTest(ResumableCompactionService::TestScenario::
|
||||
kMultipleCancelToggleResumption);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -108,13 +108,11 @@ Slice SubcompactionState::LargestUserKey() const {
|
||||
Status SubcompactionState::AddToOutput(
|
||||
const CompactionIterator& iter, bool use_proximal_output,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key) {
|
||||
const CompactionFileCloseFunc& close_file_func) {
|
||||
// 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_iter_output_internal_key);
|
||||
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -191,14 +191,6 @@ class SubcompactionState {
|
||||
return &compaction_outputs_.stats_;
|
||||
}
|
||||
|
||||
uint64_t GetWorkerCPUMicros() const {
|
||||
uint64_t rv = compaction_outputs_.GetWorkerCPUMicros();
|
||||
if (compaction->SupportsPerKeyPlacement()) {
|
||||
rv += proximal_level_outputs_.GetWorkerCPUMicros();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
CompactionRangeDelAggregator* RangeDelAgg() const {
|
||||
return range_del_agg_.get();
|
||||
}
|
||||
@@ -208,20 +200,10 @@ class SubcompactionState {
|
||||
return range_del_agg_ && !range_del_agg_->IsEmpty();
|
||||
}
|
||||
|
||||
void SetSubcompactionProgress(
|
||||
const SubcompactionProgress& subcompaction_progress) {
|
||||
subcompaction_progress_ = subcompaction_progress;
|
||||
}
|
||||
|
||||
SubcompactionProgress& GetSubcompactionProgressRef() {
|
||||
return subcompaction_progress_;
|
||||
}
|
||||
|
||||
// Add compaction_iterator key/value to the `Current` output group.
|
||||
Status AddToOutput(const CompactionIterator& iter, bool use_proximal_output,
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_iter_output_internal_key);
|
||||
const CompactionFileCloseFunc& close_file_func);
|
||||
|
||||
// Close all compaction output files, both output_to_proximal_level outputs
|
||||
// and normal outputs.
|
||||
@@ -251,8 +233,6 @@ class SubcompactionState {
|
||||
CompactionOutputs proximal_level_outputs_;
|
||||
CompactionOutputs* current_outputs_ = &compaction_outputs_;
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg_;
|
||||
|
||||
SubcompactionProgress subcompaction_progress_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1764,10 +1764,7 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
options.env = mock_env_.get();
|
||||
options.level0_file_num_compaction_trigger = kNumTrigger;
|
||||
options.num_levels = kNumLevels;
|
||||
// This existing test selected to also check the case of various temperatures
|
||||
// for last_level_temperature, which should not be interesting enough to
|
||||
// exercise across many/all test cases
|
||||
options.last_level_temperature = RandomKnownTemperature();
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -1794,10 +1791,6 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
ASSERT_FALSE(tp_mapping.Empty());
|
||||
auto seqs = tp_mapping.TEST_GetInternalMapping();
|
||||
ASSERT_FALSE(seqs.empty());
|
||||
ASSERT_GE(GetSstSizeHelper(Temperature::kUnknown), 1);
|
||||
for (auto t : kKnownTemperatures) {
|
||||
ASSERT_EQ(GetSstSizeHelper(t), 0);
|
||||
}
|
||||
|
||||
// Wait more than preclude_last_level time, then make sure all the data is
|
||||
// compacted to the last level even there's no write (no seqno -> time
|
||||
@@ -1806,14 +1799,8 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
|
||||
for (auto t : kKnownTemperatures) {
|
||||
if (t == options.last_level_temperature) {
|
||||
ASSERT_GT(GetSstSizeHelper(t), 0);
|
||||
} else {
|
||||
ASSERT_EQ(GetSstSizeHelper(t), 0);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
+24
-70
@@ -675,6 +675,30 @@ 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 {
|
||||
@@ -3868,75 +3892,6 @@ 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.
|
||||
@@ -5132,7 +5087,6 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
|
||||
Options options_disallow = options_allow;
|
||||
options_disallow.disallow_memtable_writes = true;
|
||||
options_disallow.paranoid_memory_checks = true;
|
||||
options_disallow.memtable_veirfy_per_key_checksum_on_seek = true;
|
||||
|
||||
DestroyAndReopen(options_allow);
|
||||
// CFs allowing and disallowing memtable write
|
||||
|
||||
@@ -710,20 +710,12 @@ 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", kAlwaysTrueFilterBytes);
|
||||
return Slice("\0\0\0\0\0\0", 6);
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/concurrent_task_limiter.h"
|
||||
#include "rocksdb/experimental.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -9792,7 +9791,6 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
|
||||
int total_cold = 0;
|
||||
int total_warm = 0;
|
||||
int total_hot = 0;
|
||||
int total_ice = 0;
|
||||
int total_unknown = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
|
||||
@@ -9803,8 +9801,6 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
|
||||
total_warm++;
|
||||
} else if (temperature == Temperature::kHot) {
|
||||
total_hot++;
|
||||
} else if (temperature == Temperature::kIce) {
|
||||
total_ice++;
|
||||
} else {
|
||||
assert(temperature == Temperature::kUnknown);
|
||||
total_unknown++;
|
||||
@@ -9878,261 +9874,6 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
|
||||
}
|
||||
}
|
||||
|
||||
using TemperatureSet = SmallEnumSet<Temperature, Temperature::kLastTemperature>;
|
||||
static void VerifyTemperatureFileReadStats(const Statistics& st,
|
||||
TemperatureSet temps) {
|
||||
SCOPED_TRACE("Temp set size = " + std::to_string(temps.count()));
|
||||
constexpr uint64_t min_bytes = 100;
|
||||
constexpr uint64_t min_count = 1;
|
||||
|
||||
IOStatsContext* iostats = get_iostats_context();
|
||||
if (temps.Contains(Temperature::kHot)) {
|
||||
EXPECT_GE(st.getTickerCount(HOT_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(HOT_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.hot_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.hot_file_read_count,
|
||||
min_count);
|
||||
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(HOT_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(HOT_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
}
|
||||
|
||||
if (temps.Contains(Temperature::kWarm)) {
|
||||
EXPECT_GE(st.getTickerCount(WARM_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(WARM_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.warm_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.warm_file_read_count,
|
||||
min_count);
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(WARM_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(WARM_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
|
||||
}
|
||||
|
||||
if (temps.Contains(Temperature::kCool)) {
|
||||
EXPECT_GE(st.getTickerCount(COOL_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(COOL_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.cool_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.cool_file_read_count,
|
||||
min_count);
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(COOL_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(COOL_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.cool_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.cool_file_read_count, 0);
|
||||
}
|
||||
|
||||
if (temps.Contains(Temperature::kCold)) {
|
||||
EXPECT_GE(st.getTickerCount(COLD_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(COLD_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.cold_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.cold_file_read_count,
|
||||
min_count);
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(COLD_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(COLD_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.cold_file_read_count, 0);
|
||||
}
|
||||
|
||||
if (temps.Contains(Temperature::kIce)) {
|
||||
EXPECT_GE(st.getTickerCount(ICE_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(ICE_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.ice_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.ice_file_read_count,
|
||||
min_count);
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(ICE_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(ICE_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.ice_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.ice_file_read_count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
|
||||
// Test multi-tier aging: Hot -> Warm -> Cool -> Cold -> Ice
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.num_levels = 1;
|
||||
options.max_open_files = -1;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.create_if_missing = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.no_block_cache = true; // Simplify statistics
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
|
||||
CompactionOptionsFIFO fifo_options;
|
||||
// Multi-tier aging: files age through multiple temperatures
|
||||
fifo_options.file_temperature_age_thresholds = {
|
||||
{Temperature::kWarm, 500}, // Hot -> Warm after 500s
|
||||
{Temperature::kCool, 1000}, // Warm -> Cool
|
||||
{Temperature::kCold, 1500}, // Cool -> Cold
|
||||
{Temperature::kIce, 2000} // Cold -> Ice
|
||||
};
|
||||
fifo_options.max_table_files_size = 100000000;
|
||||
fifo_options.allow_trivial_copy_when_change_temperature = true;
|
||||
options.compaction_options_fifo = fifo_options;
|
||||
options.default_write_temperature = Temperature::kHot;
|
||||
|
||||
Reopen(options);
|
||||
env_->SetMockSleep();
|
||||
|
||||
// Track all temperature file creations
|
||||
int total_hot = 0, total_warm = 0, total_cool = 0, total_cold = 0,
|
||||
total_ice = 0, total_unknown = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
|
||||
Temperature temperature = *(static_cast<Temperature*>(arg));
|
||||
switch (temperature) {
|
||||
case Temperature::kHot:
|
||||
total_hot++;
|
||||
break;
|
||||
case Temperature::kWarm:
|
||||
total_warm++;
|
||||
break;
|
||||
case Temperature::kCool:
|
||||
total_cool++;
|
||||
break;
|
||||
case Temperature::kCold:
|
||||
total_cold++;
|
||||
break;
|
||||
case Temperature::kIce:
|
||||
total_ice++;
|
||||
break;
|
||||
case Temperature::kUnknown:
|
||||
total_unknown++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Create initial three files (will start as Hot), enough to ensure key
|
||||
// range filtering will be applied in FilePicker::GetNextFile() with one
|
||||
// more file
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put(Key(0), Random::GetTLSInstance()->RandomBinaryString(100)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Test reading from Hot temperature file
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kHot);
|
||||
|
||||
// Land well into each time interval
|
||||
env_->MockSleepForSeconds(100);
|
||||
|
||||
// Age initial files to warm
|
||||
env_->MockSleepForSeconds(500);
|
||||
ASSERT_OK(Put(Key(1), Random::GetTLSInstance()->RandomBinaryString(101)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Test reading from Warm temperature file (the aged file)
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
// Verify Warm file statistics
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kWarm);
|
||||
|
||||
// Age initial files to cool
|
||||
env_->MockSleepForSeconds(500);
|
||||
ASSERT_OK(Put(Key(2), Random::GetTLSInstance()->RandomBinaryString(102)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Test reading from Cool temperature file (the aged file)
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kCool);
|
||||
|
||||
// Age initial files to cold
|
||||
env_->MockSleepForSeconds(500);
|
||||
ASSERT_OK(Put(Key(3), Random::GetTLSInstance()->RandomBinaryString(103)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Test reading from Cold temperature file (the aged file)
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kCold);
|
||||
|
||||
// Age initial files to ice
|
||||
env_->MockSleepForSeconds(500);
|
||||
ASSERT_OK(Put(Key(4), Random::GetTLSInstance()->RandomBinaryString(104)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Test reading from Ice temperature file (the aged file)
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kIce);
|
||||
|
||||
// Verify temperature progression in metadata
|
||||
ColumnFamilyMetaData metadata;
|
||||
db_->GetColumnFamilyMetaData(&metadata);
|
||||
|
||||
// Should have files at different temperatures
|
||||
std::map<Temperature, int> temp_counts;
|
||||
for (const auto& file : metadata.levels[0].files) {
|
||||
temp_counts[file.temperature]++;
|
||||
}
|
||||
|
||||
// Verify current files temperatures
|
||||
EXPECT_EQ(temp_counts[Temperature::kHot], 1);
|
||||
EXPECT_EQ(temp_counts[Temperature::kWarm], 1);
|
||||
EXPECT_EQ(temp_counts[Temperature::kCool], 1);
|
||||
EXPECT_EQ(temp_counts[Temperature::kCold], 1);
|
||||
EXPECT_EQ(temp_counts[Temperature::kIce], 3);
|
||||
|
||||
// Verify historical (and current) file temperatures
|
||||
EXPECT_EQ(total_hot, 7);
|
||||
EXPECT_EQ(total_warm, 6);
|
||||
EXPECT_EQ(total_cool, 5);
|
||||
EXPECT_EQ(total_cold, 4);
|
||||
EXPECT_EQ(total_ice, 3);
|
||||
|
||||
// Final comprehensive test: read from all temperature files
|
||||
Reopen(options);
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
// Read from all files to verify cumulative statistics
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(static_cast<unsigned>(100 + i), Get(Key(i)).size());
|
||||
}
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, TemperatureSet::All());
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DisableMultiManualCompaction) {
|
||||
const int kNumL0Files = 10;
|
||||
|
||||
@@ -11541,71 +11282,6 @@ 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() {}
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
// 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,7 +109,6 @@ 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -3561,152 +3561,6 @@ TEST_F(DBFlushTest, VerifyOutputRecordCount) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
class DBFlushSuperBlockTest
|
||||
: public DBFlushTest,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, size_t, size_t>> {
|
||||
public:
|
||||
DBFlushSuperBlockTest() : DBFlushTest() {}
|
||||
|
||||
std::string formatKey(int i) {
|
||||
int desired_length = 10;
|
||||
char buffer[64];
|
||||
snprintf(buffer, 64, "%0*d", desired_length, i);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void VerifyReadWithGet(int key_count) {
|
||||
for (int i = 0; i < key_count; ++i) {
|
||||
PinnableSlice value;
|
||||
ASSERT_OK(Get(formatKey(i), &value));
|
||||
ASSERT_EQ(value.ToString(), added_data[formatKey(i)]);
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyReadWithIterator(int key_count) {
|
||||
{
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(ReadOptions()));
|
||||
int i = 0;
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ((it->key()).ToString(), formatKey(i));
|
||||
ASSERT_EQ((it->value()).ToString(), added_data[formatKey(i)]);
|
||||
i++;
|
||||
}
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_EQ(i, key_count);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Random rnd{123};
|
||||
std::unordered_map<std::string, std::string> added_data;
|
||||
};
|
||||
|
||||
constexpr size_t kLowSpaceOverheadRatio = 256;
|
||||
|
||||
TEST_P(DBFlushSuperBlockTest, SuperBlock) {
|
||||
constexpr int key_count = 12345;
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
options.paranoid_file_checks = true;
|
||||
options.write_buffer_size = 1024 * 1024;
|
||||
BlockBasedTableOptions block_options;
|
||||
block_options.block_align = get<0>(GetParam());
|
||||
block_options.index_block_restart_interval = 3;
|
||||
block_options.super_block_alignment_size = get<1>(GetParam());
|
||||
block_options.super_block_alignment_space_overhead_ratio = get<2>(GetParam());
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_options));
|
||||
if (block_options.block_align) {
|
||||
// When block align is enabled, disable compression
|
||||
options.compression = kNoCompression;
|
||||
}
|
||||
|
||||
ASSERT_OK(options.table_factory->ValidateOptions(
|
||||
DBOptions(options), ColumnFamilyOptions(options)));
|
||||
|
||||
Reopen(options);
|
||||
|
||||
int super_block_pad_count = 0;
|
||||
int super_block_pad_exceed_limit_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::WriteMaybeCompressedBlock:"
|
||||
"SuperBlockAlignment",
|
||||
[&super_block_pad_count](void* /*arg*/) { super_block_pad_count++; });
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::WriteMaybeCompressedBlock:"
|
||||
"SuperBlockAlignmentPaddingBytesExceedLimit",
|
||||
[&super_block_pad_exceed_limit_count](void* /*arg*/) {
|
||||
super_block_pad_exceed_limit_count++;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Add lots of keys
|
||||
for (int i = 0; i < key_count; ++i) {
|
||||
added_data[formatKey(i)] = std::string(rnd.RandomString(rnd.Next() % 1000));
|
||||
ASSERT_OK(Put(formatKey(i), added_data[formatKey(i)]));
|
||||
}
|
||||
|
||||
// flush the data in memory to disk to verify with super block alignment, the
|
||||
// data could be read back properly
|
||||
Reopen(options);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// When block_align is enabled, super block is always aligned, so there should
|
||||
// be 0 padding for super block alignment
|
||||
if (block_options.super_block_alignment_size != 0 &&
|
||||
!block_options.block_align) {
|
||||
ASSERT_GT(super_block_pad_count, 0);
|
||||
} else {
|
||||
ASSERT_EQ(super_block_pad_count, 0);
|
||||
}
|
||||
|
||||
if (!block_options.block_align &&
|
||||
block_options.super_block_alignment_size != 0 &&
|
||||
block_options.super_block_alignment_space_overhead_ratio ==
|
||||
kLowSpaceOverheadRatio) {
|
||||
ASSERT_GT(super_block_pad_exceed_limit_count, 0);
|
||||
}
|
||||
|
||||
// verify the values are correct
|
||||
VerifyReadWithGet(key_count);
|
||||
Reopen(options);
|
||||
VerifyReadWithIterator(key_count);
|
||||
|
||||
// verify checksum
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
|
||||
// Reopen options and flip the option of super block configuration, read still
|
||||
// works. This verifies the forward/backward compatibility
|
||||
if (block_options.super_block_alignment_size == 0) {
|
||||
block_options.super_block_alignment_size = 16 * 1024;
|
||||
} else {
|
||||
block_options.super_block_alignment_size = 0;
|
||||
}
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_options));
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// verify the values are correct
|
||||
VerifyReadWithGet(key_count);
|
||||
Reopen(options);
|
||||
VerifyReadWithIterator(key_count);
|
||||
|
||||
// verify checksum
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
SuperBlockTests, DBFlushSuperBlockTest,
|
||||
testing::Combine(testing::Bool(), testing::Values(0, 32 * 1024, 16 * 1024),
|
||||
// Use very low space overhead ratio to test
|
||||
// the case where required padded bytes is
|
||||
// larger than the max allowed padding size
|
||||
testing::Values(4, kLowSpaceOverheadRatio)));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -370,10 +370,10 @@ TEST_F(DBFollowerTest, RetryCatchupManifestRollover) {
|
||||
|
||||
// This test creates 4 L0 files and compacts them. The follower, during catchup,
|
||||
// successfully instantiates 4 Versions corresponding to the 4 files (but
|
||||
// doesn't install them yet), followed by deleting those 4 and adding a new
|
||||
// donesn't install them yet), followed by deleting those 4 and adding a new
|
||||
// file from compaction. The test verifies that the 4 L0 files are deleted
|
||||
// correctly by the follower.
|
||||
// We use the Barrier* functions to ensure that the follower first sees the 4
|
||||
// We use teh Barrier* functions to ensure that the follower first sees the 4
|
||||
// L0 files and is able to link them, and then sees the compaction that
|
||||
// obsoletes those L0 files (so those L0 files are intermediates that it has
|
||||
// to explicitly delete). Suppose we don't have any barriers, its possible
|
||||
|
||||
+10
-27
@@ -258,10 +258,10 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
[this]() { this->TriggerPeriodicCompaction(); });
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
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));
|
||||
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));
|
||||
column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
|
||||
@@ -1069,7 +1069,7 @@ void DBImpl::DumpStats() {
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (!cfd->initialized() || cfd->IsDropped()) {
|
||||
if (!cfd->initialized()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1245,11 +1245,13 @@ Status DBImpl::SetOptions(
|
||||
WriteOptionsFile(write_options, true /*db_mutex_already_held*/);
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
assert(new_options_copy == cfd->GetLatestMutableCFOptions());
|
||||
assert(cfd->GetLatestMutableCFOptions() ==
|
||||
cfd->GetCurrentMutableCFOptions());
|
||||
assert(cfd->GetCurrentMutableCFOptions() ==
|
||||
cfd->current()->GetMutableCFOptions());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
sv_context.Clean();
|
||||
@@ -1412,7 +1414,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_->UpdatedMutableDbOptions(mutable_db_options_, &mutex_);
|
||||
versions_->ChangeFileOptions(mutable_db_options_);
|
||||
// TODO(xiez): clarify why apply optimize for read to write options
|
||||
file_options_for_compaction_ = fs_->OptimizeForCompactionTableRead(
|
||||
file_options_for_compaction_, immutable_db_options_);
|
||||
@@ -1480,12 +1482,6 @@ int DBImpl::FindMinimumEmptyLevelFitting(
|
||||
return minimum_level;
|
||||
}
|
||||
|
||||
Status DBImpl::FlushWAL(const FlushWALOptions& options) {
|
||||
WriteOptions write_options;
|
||||
write_options.rate_limiter_priority = options.rate_limiter_priority;
|
||||
return FlushWAL(write_options, options.sync);
|
||||
}
|
||||
|
||||
Status DBImpl::FlushWAL(const WriteOptions& write_options, bool sync) {
|
||||
if (manual_wal_flush_) {
|
||||
IOStatus io_s;
|
||||
@@ -5047,19 +5043,6 @@ 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_);
|
||||
@@ -5519,7 +5502,7 @@ Status DBImpl::RenameTempFileToOptionsFile(const std::string& file_name,
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
|
||||
void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* cfd) const {
|
||||
if (immutable_db_options_.enable_thread_tracking) {
|
||||
@@ -5546,7 +5529,7 @@ void DBImpl::NewThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
|
||||
void DBImpl::EraseThreadStatusCfInfo(ColumnFamilyData* /*cfd*/) const {}
|
||||
|
||||
void DBImpl::EraseThreadStatusDbInfo() const {}
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
|
||||
//
|
||||
// A global method that can dump out the build version
|
||||
|
||||
+2
-18
@@ -484,13 +484,10 @@ class DBImpl : public DB {
|
||||
const FlushOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) override;
|
||||
Status FlushWAL(bool sync) override {
|
||||
FlushWALOptions options;
|
||||
options.sync = sync;
|
||||
return FlushWAL(options);
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
return FlushWAL(WriteOptions(), sync);
|
||||
}
|
||||
|
||||
Status FlushWAL(const FlushWALOptions& options) override;
|
||||
|
||||
virtual Status FlushWAL(const WriteOptions& write_options, bool sync);
|
||||
bool WALBufferIsEmpty();
|
||||
Status SyncWAL() override;
|
||||
@@ -573,11 +570,6 @@ 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;
|
||||
|
||||
@@ -2401,14 +2393,6 @@ 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,56 +1424,6 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
|
||||
bool& compaction_released,
|
||||
size_t& moved_files, size_t& moved_bytes) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Moving %d files to level-%d\n",
|
||||
c.column_family_data()->GetName().c_str(),
|
||||
static_cast<int>(c.num_input_files(0)), c.output_level());
|
||||
|
||||
// Move files to the output level by editing the manifest
|
||||
for (unsigned int l = 0; l < c.num_input_levels(); l++) {
|
||||
if (c.level(l) == c.output_level()) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < c.num_input_files(l); i++) {
|
||||
FileMetaData* f = c.input(l, i);
|
||||
c.edit()->DeleteFile(c.level(l), f->fd.GetNumber());
|
||||
c.edit()->AddFile(c.output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->epoch_number,
|
||||
f->file_checksum, f->file_checksum_func_name,
|
||||
f->unique_id, f->compensated_range_deletion_size,
|
||||
f->tail_size, f->user_defined_timestamps_persisted);
|
||||
moved_bytes += static_cast<size_t>(c.input(l, i)->fd.GetFileSize());
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer, "[%s] Moved #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
|
||||
c.column_family_data()->GetName().c_str(), f->fd.GetNumber(),
|
||||
c.output_level(), f->fd.GetFileSize());
|
||||
}
|
||||
moved_files += c.num_input_files(l);
|
||||
}
|
||||
|
||||
// Install the new version
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
const WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
Status status = versions_->LogAndApply(
|
||||
c.column_family_data(), read_options, write_options, c.edit(), &mutex_,
|
||||
directories_.GetDbDir(), /*new_descriptor_log=*/false,
|
||||
/*column_family_options=*/nullptr,
|
||||
[&c, &compaction_released](const Status& s) {
|
||||
c.ReleaseCompactionFiles(s);
|
||||
compaction_released = true;
|
||||
});
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::CompactFilesImpl(
|
||||
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
|
||||
Version* version, const std::vector<std::string>& input_file_names,
|
||||
@@ -1561,63 +1511,6 @@ 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(
|
||||
@@ -1950,7 +1843,8 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
,
|
||||
LLONG_MAX /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID, not applicable */, mutable_cf_options.compression,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompactions, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
@@ -3967,7 +3861,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
uint64_t out_file_creation_time = static_cast<uint64_t>(tmp_current_time);
|
||||
|
||||
FileOptions copied_file_options = file_options_;
|
||||
copied_file_options.temperature = c->GetOutputTemperature();
|
||||
copied_file_options.temperature = c->output_temperature();
|
||||
std::unique_ptr<WritableFileWriter> dest_writer;
|
||||
{
|
||||
std::unique_ptr<FSWritableFile> dest_file;
|
||||
@@ -3985,7 +3879,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
"NewWritableFile %s\n"
|
||||
" out_fname=%s, temperature=%s, io_status=%s",
|
||||
c->column_family_data()->GetName().c_str(), out_fname.c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
temperature_to_string[c->output_temperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
@@ -4007,7 +3901,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
|
||||
temperature_to_string[in_file->temperature].c_str(),
|
||||
out_fname.c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
temperature_to_string[c->output_temperature()].c_str(),
|
||||
c->mutable_cf_options()
|
||||
.compaction_options_fifo.trivial_copy_buffer_size);
|
||||
// Add IO_LOW HINT for compaction
|
||||
@@ -4047,7 +3941,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
|
||||
temperature_to_string[in_file->temperature].c_str(),
|
||||
out_fname.c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
temperature_to_string[c->output_temperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
@@ -4056,15 +3950,15 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
io_s = copy_file_io_status;
|
||||
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] Failed to copy from: %s\n"
|
||||
" temperature=%s, to=%s, temperature=%s, io_status=%s",
|
||||
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
|
||||
temperature_to_string[in_file->temperature].c_str(),
|
||||
out_fname.c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] Failed to copy from: %s\n"
|
||||
" temperature=%s, to=%s, temperature=%s, io_status=%s",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
in_fname.c_str(),
|
||||
temperature_to_string[in_file->temperature].c_str(),
|
||||
out_fname.c_str(),
|
||||
temperature_to_string[c->output_temperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
@@ -4074,7 +3968,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
in_fname.c_str(),
|
||||
temperature_to_string[in_file->temperature].c_str(),
|
||||
out_fname.c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
temperature_to_string[c->output_temperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
|
||||
FileMetaData out_file_metadata{
|
||||
@@ -4086,7 +3980,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
in_file->fd.smallest_seqno,
|
||||
in_file->fd.largest_seqno,
|
||||
false /* marked_for_compact */,
|
||||
c->GetOutputTemperature() /* temperature */,
|
||||
c->output_temperature() /* temperature */,
|
||||
in_file->oldest_blob_file_number,
|
||||
in_file->oldest_ancester_time,
|
||||
out_file_creation_time,
|
||||
@@ -4155,7 +4049,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
" temperature=%s, to temperature=%s, status=%s, io_status=%s",
|
||||
c->column_family_data()->GetName().c_str(), in_fname.c_str(),
|
||||
temperature_to_string[in_file->temperature].c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
temperature_to_string[c->output_temperature()].c_str(),
|
||||
status.ToString().c_str(), io_s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
@@ -4181,6 +4075,35 @@ 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();
|
||||
@@ -4191,12 +4114,14 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
vstorage->GetNextCompactCursor(start_level, c->num_input_files(0)));
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
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;
|
||||
});
|
||||
io_s = versions_->io_status();
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), job_context->superversion_contexts.data());
|
||||
@@ -4211,7 +4136,8 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
<< "total_files_size" << moved_bytes;
|
||||
}
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer, "[%s] Moved #%d files to level-%zu %zu bytes %s: %s\n",
|
||||
log_buffer,
|
||||
"[%s] Moved #%d files to level-%d %" PRIu64 " 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));
|
||||
@@ -4502,7 +4428,7 @@ Compaction* DBImpl::CreateIntendedCompactionForwardedToBottomPriorityPool(
|
||||
c->output_level(), c->target_output_file_size(),
|
||||
c->max_compaction_bytes(), c->output_path_id(),
|
||||
c->output_compression(), c->output_compression_opts(),
|
||||
c->GetOutputTemperature(), c->max_subcompactions(),
|
||||
c->output_temperature(), c->max_subcompactions(),
|
||||
c->grandparents(), std::nullopt /* earliest_snapshot */,
|
||||
nullptr /* snapshot_checker */, c->compaction_reason());
|
||||
|
||||
|
||||
@@ -267,9 +267,6 @@ 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -617,11 +614,6 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
case kOptionsFile:
|
||||
keep = (number >= optsfile_num2);
|
||||
break;
|
||||
case kCompactionProgressFile:
|
||||
// Keep compaction progress files - they are managed
|
||||
// separately by DBImplSecondary for now
|
||||
keep = true;
|
||||
break;
|
||||
case kCurrentFile:
|
||||
case kDBLockFile:
|
||||
case kIdentityFile:
|
||||
|
||||
@@ -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->mutable_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->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(
|
||||
mutable_db_options_.manifest_preallocation_size);
|
||||
immutable_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 */,
|
||||
|
||||
+19
-684
@@ -8,12 +8,7 @@
|
||||
#include <cinttypes>
|
||||
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/log_reader.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "logging/auto_roll_logger.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
@@ -783,9 +778,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->mutable_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->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();
|
||||
@@ -828,516 +823,18 @@ Status DB::OpenAsSecondary(
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::ScanCompactionProgressFiles(
|
||||
CompactionProgressFilesScan* scan_result) {
|
||||
assert(scan_result != nullptr);
|
||||
scan_result->Clear();
|
||||
|
||||
WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
IOOptions opts;
|
||||
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::vector<std::string> all_filenames;
|
||||
s = fs_->GetChildren(secondary_path_, opts, &all_filenames, nullptr /* dbg*/);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
for (const auto& filename : all_filenames) {
|
||||
if (filename == "." || filename == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
|
||||
if (!ParseFileName(filename, &number, &type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Categorize compaction progress files
|
||||
if (type == kCompactionProgressFile) {
|
||||
if (number > scan_result->latest_progress_timestamp) {
|
||||
// Found a newer progress file
|
||||
if (scan_result->HasLatestProgressFile()) {
|
||||
// Previous "latest" becomes "old"
|
||||
scan_result->old_progress_filenames.push_back(
|
||||
scan_result->latest_progress_filename.value());
|
||||
}
|
||||
scan_result->latest_progress_timestamp = number;
|
||||
scan_result->latest_progress_filename = filename;
|
||||
} else {
|
||||
// This is an older progress file
|
||||
scan_result->old_progress_filenames.push_back(filename);
|
||||
}
|
||||
} else if (type == kTempFile &&
|
||||
filename.find(kCompactionProgressFileNamePrefix) == 0) {
|
||||
// Temporary progress files
|
||||
scan_result->temp_progress_filenames.push_back(filename);
|
||||
} else if (type == kTableFile) {
|
||||
// Collect table file numbers for CleanupPhysicalCompactionOutputFiles
|
||||
scan_result->table_file_numbers.push_back(number);
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::DeleteCompactionProgressFiles(
|
||||
const std::vector<std::string>& filenames) {
|
||||
WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
IOOptions opts;
|
||||
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
for (const auto& filename : filenames) {
|
||||
std::string file_path = secondary_path_ + "/" + filename;
|
||||
Status delete_status = fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
|
||||
if (!delete_status.ok()) {
|
||||
return delete_status;
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::CleanupOldAndTemporaryCompactionProgressFiles(
|
||||
bool preserve_latest, const CompactionProgressFilesScan& scan_result) {
|
||||
std::vector<std::string> filenames_to_delete;
|
||||
|
||||
// Always delete old progress files
|
||||
filenames_to_delete.insert(filenames_to_delete.end(),
|
||||
scan_result.old_progress_filenames.begin(),
|
||||
scan_result.old_progress_filenames.end());
|
||||
|
||||
// Always delete temp files
|
||||
filenames_to_delete.insert(filenames_to_delete.end(),
|
||||
scan_result.temp_progress_filenames.begin(),
|
||||
scan_result.temp_progress_filenames.end());
|
||||
|
||||
// Conditionally delete latest file
|
||||
if (!preserve_latest && scan_result.HasLatestProgressFile()) {
|
||||
filenames_to_delete.push_back(scan_result.latest_progress_filename.value());
|
||||
}
|
||||
|
||||
return DeleteCompactionProgressFiles(filenames_to_delete);
|
||||
}
|
||||
|
||||
// Loads compaction progress from a file and cleans up extra output
|
||||
// files. After loading the progress, this function identifies and deletes any
|
||||
// SST files in the output folder that are NOT tracked in the
|
||||
// progress. This ensures consistency between the progress file and
|
||||
// actual output files on disk.
|
||||
Status DBImplSecondary::LoadCompactionProgressAndCleanupExtraOutputFiles(
|
||||
const std::string& compaction_progress_file_path,
|
||||
const CompactionProgressFilesScan& scan_result) {
|
||||
Status s = ParseCompactionProgressFile(compaction_progress_file_path,
|
||||
&compaction_progress_);
|
||||
if (s.ok()) {
|
||||
s = CleanupPhysicalCompactionOutputFiles(true /* preserve_tracked_files */,
|
||||
scan_result);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::ParseCompactionProgressFile(
|
||||
const std::string& compaction_progress_file_path,
|
||||
CompactionProgress* compaction_progress) {
|
||||
std::unique_ptr<FSSequentialFile> file;
|
||||
Status s = fs_->NewSequentialFile(compaction_progress_file_path,
|
||||
FileOptions(), &file, nullptr /* dbg */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unique_ptr<SequentialFileReader> file_reader(new SequentialFileReader(
|
||||
std::move(file), compaction_progress_file_path,
|
||||
immutable_db_options_.log_readahead_size, io_tracer_, {} /* listeners */,
|
||||
immutable_db_options_.rate_limiter.get()));
|
||||
|
||||
Status reader_status;
|
||||
|
||||
struct CompactionProgressReaderReporter : public log::Reader::Reporter {
|
||||
Status* status;
|
||||
explicit CompactionProgressReaderReporter(Status* s) : status(s) {}
|
||||
|
||||
void Corruption(size_t /*bytes*/, const Status& s,
|
||||
uint64_t /*log_number*/) override {
|
||||
if (status->ok()) {
|
||||
*status = s;
|
||||
}
|
||||
}
|
||||
|
||||
void OldLogRecord(size_t /*bytes*/) override {
|
||||
// Ignore old records
|
||||
}
|
||||
} progress_reporter(&reader_status);
|
||||
|
||||
log::Reader compaction_progress_reader(
|
||||
immutable_db_options_.info_log, std::move(file_reader),
|
||||
&progress_reporter, true /* checksum */, 0 /* log_num */);
|
||||
|
||||
// LIMITATION: Only supports resuming single subcompaction
|
||||
SubcompactionProgressBuilder progress_builder;
|
||||
Slice slice;
|
||||
std::string record;
|
||||
|
||||
while (compaction_progress_reader.ReadRecord(&slice, &record)) {
|
||||
if (!reader_status.ok()) {
|
||||
return reader_status;
|
||||
}
|
||||
|
||||
VersionEdit edit;
|
||||
s = edit.DecodeFrom(slice);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool res = progress_builder.ProcessVersionEdit(edit);
|
||||
if (!res) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (progress_builder.HasAccumulatedSubcompactionProgress()) {
|
||||
compaction_progress->clear();
|
||||
compaction_progress->push_back(
|
||||
progress_builder.GetAccumulatedSubcompactionProgress());
|
||||
} else {
|
||||
s = Status::NotFound("No compaction progress was persisted yet");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::RenameCompactionProgressFile(
|
||||
const std::string& temp_file_path, std::string* final_file_path) {
|
||||
uint64_t current_time = env_->NowMicros();
|
||||
*final_file_path = CompactionProgressFileName(secondary_path_, current_time);
|
||||
|
||||
WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
IOOptions opts;
|
||||
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
s = fs_->RenameFile(temp_file_path, *final_file_path, opts,
|
||||
nullptr /* dbg */);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::CleanupPhysicalCompactionOutputFiles(
|
||||
bool preserve_tracked_files,
|
||||
const CompactionProgressFilesScan& scan_result) {
|
||||
std::unordered_set<uint64_t> files_to_preserve;
|
||||
|
||||
if (preserve_tracked_files) {
|
||||
for (const auto& subcompaction_progress : compaction_progress_) {
|
||||
for (const auto& file_metadata :
|
||||
subcompaction_progress.output_level_progress.GetOutputFiles()) {
|
||||
files_to_preserve.insert(file_metadata.fd.GetNumber());
|
||||
}
|
||||
for (const auto& file_metadata :
|
||||
subcompaction_progress.proximal_output_level_progress
|
||||
.GetOutputFiles()) {
|
||||
files_to_preserve.insert(file_metadata.fd.GetNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
IOOptions opts;
|
||||
Status s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
for (uint64_t file_number : scan_result.table_file_numbers) {
|
||||
bool should_delete =
|
||||
!preserve_tracked_files ||
|
||||
(files_to_preserve.find(file_number) == files_to_preserve.end());
|
||||
|
||||
if (should_delete) {
|
||||
std::string file_path = MakeTableFileName(secondary_path_, file_number);
|
||||
Status delete_status =
|
||||
fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
|
||||
if (!delete_status.ok()) {
|
||||
return delete_status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::InitializeCompactionWorkspace(
|
||||
bool allow_resumption, std::unique_ptr<FSDirectory>* output_dir,
|
||||
std::unique_ptr<log::Writer>* compaction_progress_writer) {
|
||||
// Create output directory if it doest exist yet
|
||||
Status s = CreateAndNewDirectory(fs_.get(), secondary_path_, output_dir);
|
||||
if (!s.ok() || !allow_resumption) {
|
||||
return s;
|
||||
}
|
||||
|
||||
s = PrepareCompactionProgressState();
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
s = FinalizeCompactionProgressWriter(compaction_progress_writer);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// PrepareCompactionProgressState() manages compaction progress files and output
|
||||
// files to ensure a clean, consistent state for resuming or starting fresh
|
||||
// compaction.
|
||||
//
|
||||
// PRECONDITION:
|
||||
// - This function is ONLY called when allow_resumption = true
|
||||
// - The caller wants resumption support for this compaction attempt
|
||||
//
|
||||
// FILE SYSTEM STATE (before entering this function):
|
||||
// - 0 or more compaction progress files may exist in `secondary_path_`:
|
||||
// * Latest progress file (from the most recent compaction attempt)
|
||||
// * Older progress files (left by crashing during a previous
|
||||
// InitializeCompactionWorkspace() call)
|
||||
// * Temporary progress files (left by crashing during a previous
|
||||
// InitializeCompactionWorkspace() call)
|
||||
// - 0 or more compaction output files may exist in `secondary_path_`
|
||||
//
|
||||
// POSTCONDITIONS (after this function):
|
||||
// - IF the latest progress file exists AND it parses successfully AND
|
||||
// actually contains valid compaction progress:
|
||||
// * Exactly one latest progress file remains
|
||||
// * All older and temporary compaction progress files are deleted
|
||||
// * All corresponding compaction output files are preserved
|
||||
// * All extra compaction output files are deleted (files left by
|
||||
// compaction
|
||||
// crashing before persisting the progress)
|
||||
// * Result: Ready to resume compaction from the saved progress
|
||||
// - OTHERWISE (no latest progress file OR it fails to parse OR it's
|
||||
// invalid):
|
||||
// * ALL compaction progress files are deleted (latest + older +
|
||||
// temporary)
|
||||
// * ALL compaction output files are deleted
|
||||
// * Result: Ready to start fresh compaction (despite allow_resumption =
|
||||
// true, we cannot resume because there's no valid progress to resume from)
|
||||
//
|
||||
// ERROR HANDLING:
|
||||
// - ON ERROR (if any of the postconditions cannot be achieved):
|
||||
// * Function returns error status
|
||||
// * File system may be left in a partially modified state
|
||||
// * Caller should manually clean up secondary_path_ before retrying
|
||||
// * Subsequent OpenAndCompact() calls to this clean secondary_path_ will
|
||||
// effectively start fresh compaction
|
||||
Status DBImplSecondary::PrepareCompactionProgressState() {
|
||||
Status s;
|
||||
|
||||
// STEP 1: Scan directory ONCE (includes progress files + table files)
|
||||
CompactionProgressFilesScan scan_result;
|
||||
s = ScanCompactionProgressFiles(&scan_result);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Encountered error when scanning for compaction "
|
||||
"progress files: %s",
|
||||
s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
std::optional<std::string> latest_progress_file =
|
||||
scan_result.latest_progress_filename;
|
||||
|
||||
// STEP 2: Determine if we should resume
|
||||
bool should_resume = false;
|
||||
if (latest_progress_file.has_value()) {
|
||||
should_resume = true;
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Did not find any latest compaction progress file. "
|
||||
"Will perform clean up to start fresh compaction");
|
||||
}
|
||||
|
||||
// STEP 3: Cleanup using pre-scanned results
|
||||
if (should_resume) {
|
||||
// Keep latest, delete old/temp
|
||||
s = CleanupOldAndTemporaryCompactionProgressFiles(
|
||||
true /* preserve_latest */, scan_result);
|
||||
} else {
|
||||
// Delete everything including latest
|
||||
s = CleanupOldAndTemporaryCompactionProgressFiles(
|
||||
false /* preserve_latest */, scan_result);
|
||||
latest_progress_file.reset();
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Failed to clean up compaction progress file(s): %s. "
|
||||
"Will fail the compaction",
|
||||
s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
// STEP 4: Load progress if resuming
|
||||
if (latest_progress_file.has_value()) {
|
||||
uint64_t timestamp = scan_result.latest_progress_timestamp;
|
||||
|
||||
std::string compaction_progress_file_path =
|
||||
CompactionProgressFileName(secondary_path_, timestamp);
|
||||
|
||||
s = LoadCompactionProgressAndCleanupExtraOutputFiles(
|
||||
compaction_progress_file_path, scan_result);
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to load the latest compaction "
|
||||
"progress from %s: %s. Will perform clean up "
|
||||
"to start fresh compaction",
|
||||
latest_progress_file.value().c_str(),
|
||||
s.ToString().c_str());
|
||||
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(
|
||||
std::nullopt /* compaction_progress_file_path */, scan_result);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t DBImplSecondary::CalculateResumedCompactionBytes(
|
||||
const CompactionProgress& compaction_progress) const {
|
||||
uint64_t total_resumed_bytes = 0;
|
||||
|
||||
for (const auto& subcompaction_progress : compaction_progress) {
|
||||
for (const auto& file_meta :
|
||||
subcompaction_progress.output_level_progress.GetOutputFiles()) {
|
||||
total_resumed_bytes += file_meta.fd.file_size;
|
||||
}
|
||||
|
||||
for (const auto& file_meta :
|
||||
subcompaction_progress.proximal_output_level_progress
|
||||
.GetOutputFiles()) {
|
||||
total_resumed_bytes += file_meta.fd.file_size;
|
||||
}
|
||||
}
|
||||
|
||||
return total_resumed_bytes;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::HandleInvalidOrNoCompactionProgress(
|
||||
const std::optional<std::string>& compaction_progress_file_path,
|
||||
const CompactionProgressFilesScan& scan_result) {
|
||||
compaction_progress_.clear();
|
||||
|
||||
Status s;
|
||||
if (compaction_progress_file_path.has_value()) {
|
||||
WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
IOOptions opts;
|
||||
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (s.ok()) {
|
||||
s = fs_->DeleteFile(compaction_progress_file_path.value(), opts,
|
||||
nullptr /* dbg */);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Failed to remove invalid progress file: %s",
|
||||
s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
s = CleanupPhysicalCompactionOutputFiles(false /* preserve_tracked_files */,
|
||||
scan_result);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Failed to cleanup existing compaction output files: %s",
|
||||
s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::CompactWithoutInstallation(
|
||||
const OpenAndCompactOptions& options, ColumnFamilyHandle* cfh,
|
||||
const CompactionServiceInput& input, CompactionServiceResult* result) {
|
||||
if (options.canceled && options.canceled->load(std::memory_order_acquire)) {
|
||||
return Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
|
||||
std::unique_ptr<FSDirectory> output_dir;
|
||||
std::unique_ptr<log::Writer> compaction_progress_writer;
|
||||
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
auto cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
|
||||
if (!cfd) {
|
||||
return Status::InvalidArgument("Cannot find column family" +
|
||||
cfh->GetName());
|
||||
}
|
||||
Status s;
|
||||
|
||||
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
|
||||
// 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 "
|
||||
"incompatibility with output hash verification "
|
||||
"(paranoid_file_checks=true or verify_output_flags "
|
||||
"containing kVerifyIteration)");
|
||||
}
|
||||
|
||||
mutex_.Unlock();
|
||||
|
||||
s = InitializeCompactionWorkspace(allow_resumption, &output_dir,
|
||||
&compaction_progress_writer);
|
||||
|
||||
mutex_.Lock();
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unordered_set<uint64_t> input_set;
|
||||
for (const auto& file_name : input.input_files) {
|
||||
@@ -1354,12 +851,12 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
CompactionOptions comp_options;
|
||||
comp_options.compression = kDisableCompressionOption;
|
||||
comp_options.output_file_size_limit = MaxFileSizeForLevel(
|
||||
mutable_cf_options, input.output_level, cfd->ioptions().compaction_style,
|
||||
vstorage->base_level(),
|
||||
cfd->GetLatestMutableCFOptions(), input.output_level,
|
||||
cfd->ioptions().compaction_style, vstorage->base_level(),
|
||||
cfd->ioptions().level_compaction_dynamic_level_bytes);
|
||||
|
||||
std::vector<CompactionInputFiles> input_files;
|
||||
s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
|
||||
Status s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage, comp_options);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(
|
||||
@@ -1382,22 +879,23 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
// input instead of recreating it in the remote worker
|
||||
std::unique_ptr<Compaction> c;
|
||||
assert(cfd->compaction_picker());
|
||||
std::optional<SequenceNumber> earliest_snapshot = std::nullopt;
|
||||
// Standalone Range Deletion Optimization is only supported in Universal
|
||||
// Compactions - https://github.com/facebook/rocksdb/pull/13078
|
||||
if (cfd->GetLatestCFOptions().compaction_style ==
|
||||
CompactionStyle::kCompactionStyleUniversal) {
|
||||
earliest_snapshot = !job_context.snapshot_seqs.empty()
|
||||
? job_context.snapshot_seqs.front()
|
||||
: kMaxSequenceNumber;
|
||||
}
|
||||
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
|
||||
comp_options, input_files, input.output_level, vstorage,
|
||||
mutable_cf_options, mutable_db_options_, 0, earliest_snapshot,
|
||||
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0,
|
||||
/*earliest_snapshot=*/job_context.snapshot_seqs.empty()
|
||||
? kMaxSequenceNumber
|
||||
: job_context.snapshot_seqs.front(),
|
||||
job_context.snapshot_checker));
|
||||
assert(c != nullptr);
|
||||
c->FinalizeInputInfo(version);
|
||||
|
||||
// Create output directory if it's not existed yet
|
||||
std::unique_ptr<FSDirectory> output_dir;
|
||||
s = CreateAndNewDirectory(fs_.get(), secondary_path_, &output_dir);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
|
||||
@@ -1415,15 +913,13 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
options.canceled ? *options.canceled : kManualCompactionCanceledFalse_,
|
||||
input.db_id, db_session_id_, secondary_path_, input, result);
|
||||
|
||||
compaction_job.Prepare(compaction_progress_,
|
||||
compaction_progress_writer.get());
|
||||
compaction_job.Prepare();
|
||||
|
||||
mutex_.Unlock();
|
||||
s = compaction_job.Run();
|
||||
mutex_.Lock();
|
||||
|
||||
// These cleanup functions handle metadata and state cleanup only and
|
||||
// not the physical files
|
||||
// clean up
|
||||
compaction_job.io_status().PermitUncheckedError();
|
||||
compaction_job.CleanupCompaction();
|
||||
c->ReleaseCompactionFiles(s);
|
||||
@@ -1431,18 +927,6 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("DBImplSecondary::CompactWithoutInstallation::End",
|
||||
&s);
|
||||
|
||||
if (!compaction_progress_.empty() && s.ok()) {
|
||||
uint64_t total_resumed_bytes =
|
||||
CalculateResumedCompactionBytes(compaction_progress_);
|
||||
|
||||
if (total_resumed_bytes > 0 &&
|
||||
immutable_db_options_.statistics != nullptr) {
|
||||
RecordTick(immutable_db_options_.statistics.get(),
|
||||
REMOTE_COMPACT_RESUMED_BYTES, total_resumed_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
result->status = s;
|
||||
return s;
|
||||
}
|
||||
@@ -1598,153 +1082,4 @@ Status DB::OpenAndCompact(
|
||||
output, override_options);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::CreateCompactionProgressWriter(
|
||||
const std::string& file_path,
|
||||
std::unique_ptr<log::Writer>* compaction_progress_writer) {
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
Status s =
|
||||
fs_->NewWritableFile(file_path, FileOptions(), &file, nullptr /* dbg */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), file_path, FileOptions()));
|
||||
|
||||
compaction_progress_writer->reset(
|
||||
new log::Writer(std::move(file_writer), 0 /* log_number */,
|
||||
false /* recycle_log_files */));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::PersistInitialCompactionProgress(
|
||||
log::Writer* compaction_progress_writer,
|
||||
const CompactionProgress& compaction_progress) {
|
||||
assert(compaction_progress_writer);
|
||||
|
||||
// LIMITATION: Only supports resuming single subcompaction
|
||||
assert(compaction_progress.size() == 1);
|
||||
const SubcompactionProgress& subcompaction_progress = compaction_progress[0];
|
||||
|
||||
VersionEdit edit;
|
||||
edit.SetSubcompactionProgress(subcompaction_progress);
|
||||
|
||||
std::string record;
|
||||
if (!edit.EncodeTo(&record)) {
|
||||
return Status::IOError("Failed to encode the initial compaction progress");
|
||||
}
|
||||
|
||||
WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
Status s = compaction_progress_writer->AddRecord(write_options, record);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
IOOptions opts;
|
||||
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
s = compaction_progress_writer->file()->Sync(opts,
|
||||
immutable_db_options_.use_fsync);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::HandleCompactionProgressWriterCreationFailure(
|
||||
const std::string& temp_file_path, const std::string& final_file_path,
|
||||
std::unique_ptr<log::Writer>* compaction_progress_writer) {
|
||||
compaction_progress_writer->reset();
|
||||
|
||||
const std::vector<std::string> paths_to_delete = {final_file_path,
|
||||
temp_file_path};
|
||||
|
||||
Status s;
|
||||
for (const auto& file_path : paths_to_delete) {
|
||||
WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
IOOptions opts;
|
||||
s = WritableFileWriter::PrepareIOOptions(write_options, opts);
|
||||
if (s.ok()) {
|
||||
s = fs_->DeleteFile(file_path, opts, nullptr /* dbg */);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Failed to cleanup the compaction progress file "
|
||||
"during writer creation failure: %s",
|
||||
s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImplSecondary::FinalizeCompactionProgressWriter(
|
||||
std::unique_ptr<log::Writer>* compaction_progress_writer) {
|
||||
uint64_t timestamp = env_->NowMicros();
|
||||
const std::string temp_file_path =
|
||||
TempCompactionProgressFileName(secondary_path_, timestamp);
|
||||
|
||||
Status s = CreateCompactionProgressWriter(temp_file_path,
|
||||
compaction_progress_writer);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to create compaction progress writer at "
|
||||
"temp path %s: %s. Will perform clean up "
|
||||
"to start compaction without progress persistence",
|
||||
temp_file_path.c_str(), s.ToString().c_str());
|
||||
return HandleCompactionProgressWriterCreationFailure(
|
||||
temp_file_path, "" /* final_file_path */, compaction_progress_writer);
|
||||
}
|
||||
|
||||
if (!compaction_progress_.empty()) {
|
||||
s = PersistInitialCompactionProgress(compaction_progress_writer->get(),
|
||||
compaction_progress_);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to persist the initial copmaction "
|
||||
"progress: %s. Will perform clean up "
|
||||
"to start compaction without progress persistence",
|
||||
s.ToString().c_str());
|
||||
return HandleCompactionProgressWriterCreationFailure(
|
||||
temp_file_path, "" /* final_file_path */, compaction_progress_writer);
|
||||
}
|
||||
}
|
||||
|
||||
compaction_progress_writer->reset();
|
||||
|
||||
std::string final_file_path;
|
||||
s = RenameCompactionProgressFile(temp_file_path, &final_file_path);
|
||||
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to rename temporary compaction progress "
|
||||
"file from %s to %s: %s. Will perform clean up "
|
||||
"to start compaction without progress persistence",
|
||||
temp_file_path.c_str(), final_file_path.c_str(),
|
||||
s.ToString().c_str());
|
||||
return HandleCompactionProgressWriterCreationFailure(
|
||||
temp_file_path, final_file_path, compaction_progress_writer);
|
||||
}
|
||||
|
||||
s = CreateCompactionProgressWriter(final_file_path,
|
||||
compaction_progress_writer);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Failed to create the final compaction progress "
|
||||
"writer: %s. Will attempt clean to start the compaction "
|
||||
"without progress persistence",
|
||||
s.ToString().c_str());
|
||||
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
|
||||
@@ -303,87 +303,6 @@ class DBImplSecondary : public DBImpl {
|
||||
const CompactionServiceInput& input,
|
||||
CompactionServiceResult* result);
|
||||
|
||||
private:
|
||||
// Holds results of compaction progress files and output files from a single
|
||||
// directory scan
|
||||
struct CompactionProgressFilesScan {
|
||||
// The latest (newest) progress file filename
|
||||
std::optional<std::string> latest_progress_filename;
|
||||
uint64_t latest_progress_timestamp = 0;
|
||||
|
||||
// Older progress file filenames (to be deleted)
|
||||
autovector<std::string> old_progress_filenames;
|
||||
|
||||
// Temporary progress file filenames (to be deleted)
|
||||
autovector<std::string> temp_progress_filenames;
|
||||
|
||||
// All output file numbers - for cleanup optimization
|
||||
std::vector<uint64_t> table_file_numbers;
|
||||
|
||||
bool HasLatestProgressFile() const {
|
||||
return latest_progress_filename.has_value();
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
latest_progress_filename.reset();
|
||||
latest_progress_timestamp = 0;
|
||||
old_progress_filenames.clear();
|
||||
temp_progress_filenames.clear();
|
||||
table_file_numbers.clear();
|
||||
}
|
||||
};
|
||||
|
||||
Status InitializeCompactionWorkspace(
|
||||
bool allow_resumption, std::unique_ptr<FSDirectory>* output_dir,
|
||||
std::unique_ptr<log::Writer>* compaction_progress_writer);
|
||||
|
||||
Status PrepareCompactionProgressState();
|
||||
|
||||
Status ScanCompactionProgressFiles(CompactionProgressFilesScan* scan_result);
|
||||
|
||||
Status DeleteCompactionProgressFiles(
|
||||
const std::vector<std::string>& filenames);
|
||||
|
||||
Status CleanupOldAndTemporaryCompactionProgressFiles(
|
||||
bool preserve_latest, const CompactionProgressFilesScan& scan_result);
|
||||
|
||||
Status LoadCompactionProgressAndCleanupExtraOutputFiles(
|
||||
const std::string& compaction_progress_file_path,
|
||||
const CompactionProgressFilesScan& scan_result);
|
||||
|
||||
Status ParseCompactionProgressFile(
|
||||
const std::string& compaction_progress_file_path,
|
||||
CompactionProgress* compaction_progress);
|
||||
|
||||
Status HandleInvalidOrNoCompactionProgress(
|
||||
const std::optional<std::string>& compaction_progress_file_path,
|
||||
const CompactionProgressFilesScan& scan_result);
|
||||
|
||||
Status CleanupPhysicalCompactionOutputFiles(
|
||||
bool preserve_tracked_files,
|
||||
const CompactionProgressFilesScan& scan_result);
|
||||
|
||||
Status FinalizeCompactionProgressWriter(
|
||||
std::unique_ptr<log::Writer>* compaction_progress_writer);
|
||||
|
||||
Status CreateCompactionProgressWriter(
|
||||
const std::string& file_path,
|
||||
std::unique_ptr<log::Writer>* compaction_progress_writer);
|
||||
|
||||
Status PersistInitialCompactionProgress(
|
||||
log::Writer* compaction_progress_writer,
|
||||
const CompactionProgress& compaction_progress);
|
||||
|
||||
Status RenameCompactionProgressFile(const std::string& temp_file_path,
|
||||
std::string* final_file_path);
|
||||
|
||||
Status HandleCompactionProgressWriterCreationFailure(
|
||||
const std::string& temp_file_path, const std::string& final_file_path,
|
||||
std::unique_ptr<log::Writer>* compaction_progress_writer);
|
||||
|
||||
uint64_t CalculateResumedCompactionBytes(
|
||||
const CompactionProgress& compaction_progress) const;
|
||||
|
||||
// Cache log readers for each log number, used for continue WAL replay
|
||||
// after recovery
|
||||
std::map<uint64_t, std::unique_ptr<LogReaderContainer>> log_readers_;
|
||||
@@ -392,8 +311,6 @@ class DBImplSecondary : public DBImpl {
|
||||
std::unordered_map<ColumnFamilyData*, uint64_t> cfd_to_current_log_;
|
||||
|
||||
const std::string secondary_path_;
|
||||
|
||||
CompactionProgress compaction_progress_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
-104
@@ -1565,115 +1565,11 @@ void DBIter::SetSavedKeyToSeekForPrevTarget(const Slice& target) {
|
||||
}
|
||||
}
|
||||
|
||||
Status DBIter::ValidateScanOptions(const MultiScanArgs& multiscan_opts) const {
|
||||
if (multiscan_opts.empty()) {
|
||||
return Status::InvalidArgument("Empty MultiScanArgs");
|
||||
}
|
||||
|
||||
const std::vector<ScanOptions>& scan_opts = multiscan_opts.GetScanRanges();
|
||||
const bool has_limit = scan_opts.front().range.limit.has_value();
|
||||
if (!has_limit && scan_opts.size() > 1) {
|
||||
return Status::InvalidArgument("Scan has no upper bound");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < scan_opts.size(); ++i) {
|
||||
const auto& scan_range = scan_opts[i].range;
|
||||
if (!scan_range.start.has_value()) {
|
||||
return Status::InvalidArgument("Scan has no start key at index " +
|
||||
std::to_string(i));
|
||||
}
|
||||
|
||||
if (scan_range.limit.has_value()) {
|
||||
if (user_comparator_.CompareWithoutTimestamp(
|
||||
scan_range.start.value(), /*a_has_ts=*/false,
|
||||
scan_range.limit.value(), /*b_has_ts=*/false) >= 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Scan start key is large or equal than limit at index " +
|
||||
std::to_string(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
if (!scan_range.limit.has_value()) {
|
||||
// multiple scan without limit scan ranges
|
||||
return Status::InvalidArgument("Scan has no upper bound at index " +
|
||||
std::to_string(i));
|
||||
}
|
||||
|
||||
const auto& last_end_key = scan_opts[i - 1].range.limit.value();
|
||||
if (user_comparator_.CompareWithoutTimestamp(
|
||||
scan_range.start.value(), /*a_has_ts=*/false, last_end_key,
|
||||
/*b_has_ts=*/false) < 0) {
|
||||
return Status::InvalidArgument("Overlapping ranges at index " +
|
||||
std::to_string(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBIter::Prepare(const MultiScanArgs& scan_opts) {
|
||||
status_ = ValidateScanOptions(scan_opts);
|
||||
if (!status_.ok()) {
|
||||
return;
|
||||
}
|
||||
std::optional<MultiScanArgs> new_scan_opts;
|
||||
new_scan_opts.emplace(scan_opts);
|
||||
scan_opts_.swap(new_scan_opts);
|
||||
scan_index_ = 0;
|
||||
if (!scan_opts.empty()) {
|
||||
iter_.Prepare(&scan_opts_.value());
|
||||
} else {
|
||||
iter_.Prepare(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
PERF_COUNTER_ADD(iter_seek_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
|
||||
StopWatch sw(clock_, statistics_, DB_SEEK);
|
||||
|
||||
if (scan_opts_.has_value()) {
|
||||
// Validate the seek target is as expected in the previously prepared range
|
||||
auto const& scan_ranges = scan_opts_.value().GetScanRanges();
|
||||
if (scan_index_ >= scan_ranges.size()) {
|
||||
status_ = Status::InvalidArgument(
|
||||
"Seek called after exhausting all of the scan ranges");
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate start key of next prepare range matches the seek target
|
||||
auto const& range = scan_ranges[scan_index_];
|
||||
auto const& start = range.range.start;
|
||||
assert(start.has_value());
|
||||
if (user_comparator_.CompareWithoutTimestamp(target, *start) != 0) {
|
||||
status_ = Status::InvalidArgument(
|
||||
"Seek target does not match the start of the next prepared range at "
|
||||
"index " +
|
||||
std::to_string(scan_index_));
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// validate the upper bound is set to the same value of limit, if limit
|
||||
// exists
|
||||
auto const& limit = range.range.limit;
|
||||
if (limit.has_value()) {
|
||||
if (iterate_upper_bound_ == nullptr ||
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
limit.value(), *iterate_upper_bound_) != 0) {
|
||||
status_ = Status::InvalidArgument(
|
||||
"Upper bound is not set to the same limit value of the next "
|
||||
"prepared range at index " +
|
||||
std::to_string(scan_index_));
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
scan_index_++;
|
||||
}
|
||||
|
||||
if (cfh_ != nullptr) {
|
||||
// TODO: What do we do if this returns an error?
|
||||
Slice lower_bound, upper_bound;
|
||||
|
||||
+10
-3
@@ -240,8 +240,16 @@ class DBIter final : public Iterator {
|
||||
|
||||
bool PrepareValue() override;
|
||||
|
||||
void Prepare(const MultiScanArgs& scan_opts) override;
|
||||
Status ValidateScanOptions(const MultiScanArgs& multiscan_opts) const;
|
||||
void Prepare(const MultiScanArgs& scan_opts) override {
|
||||
std::optional<MultiScanArgs> new_scan_opts;
|
||||
new_scan_opts.emplace(scan_opts);
|
||||
scan_opts_.swap(new_scan_opts);
|
||||
if (!scan_opts.empty()) {
|
||||
iter_.Prepare(&scan_opts_.value());
|
||||
} else {
|
||||
iter_.Prepare(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
DBIter(Env* _env, const ReadOptions& read_options,
|
||||
@@ -498,7 +506,6 @@ class DBIter final : public Iterator {
|
||||
const size_t timestamp_size_;
|
||||
std::string saved_timestamp_;
|
||||
std::optional<MultiScanArgs> scan_opts_;
|
||||
size_t scan_index_{0};
|
||||
ReadOnlyMemTable* const active_mem_;
|
||||
SequenceNumber memtable_seqno_lb_;
|
||||
uint32_t memtable_op_scan_flush_trigger_;
|
||||
|
||||
+66
-718
@@ -4149,14 +4149,10 @@ class DBMultiScanIteratorTest : public DBTestBase,
|
||||
: DBTestBase("db_multi_scan_iterator_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
// Param 0: ReadOptions::fill_cache
|
||||
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,11 +4192,69 @@ TEST_P(DBMultiScanIteratorTest, BasicTest) {
|
||||
abort();
|
||||
}
|
||||
iter.reset();
|
||||
|
||||
// Test the overlapping scan case
|
||||
key_ranges[1] = "k30";
|
||||
scan_options = MultiScanArgs(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
|
||||
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
int idx = 0;
|
||||
int count = 0;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString().compare(key_ranges[idx]), 0);
|
||||
ASSERT_LT(it.first.ToString().compare(key_ranges[idx + 1]), 0);
|
||||
count++;
|
||||
}
|
||||
idx += 2;
|
||||
}
|
||||
ASSERT_EQ(count, 52);
|
||||
} 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 the no limit scan case
|
||||
scan_options = MultiScanArgs(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0]);
|
||||
scan_options.insert(key_ranges[2]);
|
||||
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
int idx = 0;
|
||||
int count = 0;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString().compare(key_ranges[idx]), 0);
|
||||
if (it.first.ToString().compare(key_ranges[idx + 1]) == 0) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
idx += 2;
|
||||
}
|
||||
ASSERT_EQ(count, 52);
|
||||
} 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, MixedBoundsTest) {
|
||||
auto options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
// Create a file
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
@@ -4312,720 +4366,14 @@ TEST_P(DBMultiScanIteratorTest, RangeAcrossFiles) {
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
int i = 10;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_EQ(it.first.ToString(), Key(i));
|
||||
++i;
|
||||
}
|
||||
int i = 10;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_EQ(it.first.ToString(), Key(i));
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(i, 90);
|
||||
} 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, FailureTest) {
|
||||
auto options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
// Create a file
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(2) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> key_ranges({"k04", "k06", "k12", "k14"});
|
||||
ReadOptions ro;
|
||||
Slice ub;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
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]);
|
||||
scan_options.max_prefetch_size = 4500;
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
|
||||
ASSERT_NE(iter, nullptr);
|
||||
iter->Prepare(scan_options);
|
||||
int count = 0;
|
||||
ub = key_ranges[1];
|
||||
iter->Seek(key_ranges[0]);
|
||||
while (iter->status().ok() && iter->Valid()) {
|
||||
ASSERT_GE(iter->key().compare(key_ranges[0]), 0);
|
||||
ASSERT_LT(iter->key().compare(key_ranges[1]), 0);
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status()) << iter->status().ToString();
|
||||
ASSERT_EQ(count, 2);
|
||||
|
||||
// Second seek should hit the max_prefetch_size limit
|
||||
ub = key_ranges[3];
|
||||
iter->Seek(key_ranges[2]);
|
||||
ASSERT_NOK(iter->status());
|
||||
iter.reset();
|
||||
|
||||
// Test the case of unexpected Seek key
|
||||
iter.reset(dbfull()->NewIterator(ro, cfh));
|
||||
ASSERT_NE(iter, nullptr);
|
||||
scan_options.max_prefetch_size = 0;
|
||||
iter->Prepare(scan_options);
|
||||
ub = key_ranges[3];
|
||||
iter->Seek(key_ranges[2]);
|
||||
ASSERT_NOK(iter->status());
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, OutOfL0FileRange) {
|
||||
// Test that prepare does not fail scan when a scan range
|
||||
// is outside of a L0 file's key range.
|
||||
auto options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
// Create a Lmax file
|
||||
// key01 ~ key99
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(2) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
// Create a L0 file
|
||||
// key00 ~ key09
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(2) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
|
||||
// The second range is outside of L0 file's key range
|
||||
std::vector<std::string> key_ranges({"k04", "k06", "k12", "k14"});
|
||||
ReadOptions ro;
|
||||
Slice ub;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
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<Iterator> iter(dbfull()->NewIterator(ro, cfh));
|
||||
ASSERT_NE(iter, nullptr);
|
||||
iter->Prepare(scan_options);
|
||||
int count = 0;
|
||||
ub = key_ranges[1];
|
||||
iter->Seek(key_ranges[0]);
|
||||
while (iter->status().ok() && iter->Valid()) {
|
||||
ASSERT_GE(iter->key().compare(key_ranges[0]), 0);
|
||||
ASSERT_LT(iter->key().compare(key_ranges[1]), 0);
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status()) << iter->status().ToString();
|
||||
ASSERT_EQ(count, 2);
|
||||
|
||||
ub = key_ranges[3];
|
||||
count = 0;
|
||||
iter->Seek(key_ranges[2]);
|
||||
while (iter->status().ok() && iter->Valid()) {
|
||||
ASSERT_GE(iter->key().compare(key_ranges[2]), 0);
|
||||
ASSERT_LT(iter->key().compare(key_ranges[3]), 0);
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status()) << iter->status().ToString();
|
||||
ASSERT_EQ(count, 2);
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, RangeBetweenFiles) {
|
||||
auto options = CurrentOptions();
|
||||
options.target_file_size_base = 100 << 10; // 20KB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
auto rnd = Random::GetTLSInstance();
|
||||
// Write ~200KB data
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ASSERT_OK(Put(Key(i), rnd->RandomString(2 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(49));
|
||||
|
||||
// Test with a scan range that overlaps an entire file, with upper bound
|
||||
// between 2 files
|
||||
std::vector<LiveFileMetaData> file_meta;
|
||||
dbfull()->GetLiveFilesMetaData(&file_meta);
|
||||
ASSERT_EQ(file_meta.size(), 2);
|
||||
std::vector<std::string> key_ranges(4);
|
||||
key_ranges[0] = file_meta[0].smallestkey;
|
||||
key_ranges[1] = file_meta[0].largestkey + "0";
|
||||
key_ranges[2] = file_meta[1].smallestkey + "0";
|
||||
key_ranges[3] = file_meta[1].largestkey;
|
||||
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);
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[0]);
|
||||
}
|
||||
}
|
||||
} 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 multiscan with a range entirely between adjacent files
|
||||
key_ranges[0] = file_meta[0].largestkey + "0";
|
||||
key_ranges[1] = file_meta[0].largestkey + "1";
|
||||
key_ranges[2] = file_meta[1].smallestkey + "0";
|
||||
key_ranges[3] = file_meta[1].largestkey;
|
||||
(*scan_options).clear();
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[0]);
|
||||
}
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
|
||||
// This test case tests multiscan in the presence of fragmented range
|
||||
// tombstones in the LSM.
|
||||
TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
|
||||
auto options = CurrentOptions();
|
||||
// Compaction may create files 2x the target_file_size_base,
|
||||
// so set this to 50KB so we atleast end up with 2 files of
|
||||
// 100KB
|
||||
options.target_file_size_base = 50 << 10; // 50KB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Setup the LSM as follows -
|
||||
// 1. Ingest a file with 100 keys
|
||||
// 2. Ingest a file with one overlapping key
|
||||
// 3. Do a Put and flush a file to L0 with one overlapping key
|
||||
// 4. Ingest a standalone delete range file that covers the full key space
|
||||
// and a file with the same 100 keys with new values. This will ingest
|
||||
// into L0 due to the presence of an existing file in L0
|
||||
// The final LSM will have an SST in Lmax with 100 keys, and 2 SST files
|
||||
// in Lmax-1 with half the keys each and completely overlapping delete ranges
|
||||
std::unordered_map<std::string, std::string> kvs;
|
||||
auto rnd = Random::GetTLSInstance();
|
||||
auto create_ingestion_data_file_and_update_key_value =
|
||||
[&](const std::string& filename, int start_key, int end_key) {
|
||||
std::unique_ptr<SstFileWriter> writer;
|
||||
writer.reset(new SstFileWriter(EnvOptions(), options));
|
||||
ASSERT_OK(writer->Open(filename));
|
||||
for (int i = start_key; i < end_key; ++i) {
|
||||
auto kiter = kvs.find(Key(i));
|
||||
if (kiter != kvs.end()) {
|
||||
kvs.erase(kiter);
|
||||
}
|
||||
auto res =
|
||||
kvs.emplace(std::make_pair(Key(i), rnd->RandomString(2 << 10)));
|
||||
ASSERT_OK(writer->Put(res.first->first, res.first->second));
|
||||
}
|
||||
ASSERT_OK(writer->Finish());
|
||||
writer.reset();
|
||||
};
|
||||
|
||||
CreateColumnFamilies({"new_cf"}, options);
|
||||
std::string ingest_file = dbname_ + "test.sst";
|
||||
// Write ~200KB data
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_0", 0, 100);
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_1", 50, 51);
|
||||
ColumnFamilyHandle* cfh = handles_[0];
|
||||
IngestExternalFileOptions ifo;
|
||||
Status s = dbfull()->IngestExternalFile(
|
||||
cfh, {ingest_file + "_0", ingest_file + "_1"}, ifo);
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(Put(0, Key(50), rnd->RandomString(2 << 10)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
{
|
||||
std::unique_ptr<SstFileWriter> writer;
|
||||
writer.reset(new SstFileWriter(EnvOptions(), options));
|
||||
ASSERT_OK(writer->Open(ingest_file + "_2"));
|
||||
ASSERT_OK(writer->DeleteRange("a", "z"));
|
||||
ASSERT_OK(writer->Finish());
|
||||
writer.reset();
|
||||
}
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_3", 0, 100);
|
||||
s = dbfull()->IngestExternalFile(
|
||||
cfh, {ingest_file + "_2", ingest_file + "_3"}, ifo);
|
||||
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
|
||||
std::vector<std::string> key_ranges({"key000085", "key000090", "l", "n"});
|
||||
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]);
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
int i = 0;
|
||||
int count = 0;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[i]);
|
||||
ASSERT_LT(it.first.ToString(), key_ranges[i + 1]);
|
||||
auto kiter = kvs.find(it.first.ToString());
|
||||
ASSERT_NE(kiter, kvs.end());
|
||||
ASSERT_EQ(kiter->second, it.second.ToString());
|
||||
count++;
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
ASSERT_EQ(i, 4);
|
||||
ASSERT_EQ(count, 5);
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_OK(ex.status());
|
||||
}
|
||||
iter.reset();
|
||||
|
||||
// The second scan range start overlaps the delete range in the first file
|
||||
// in Lmax-1, while the end overlaps the keys in the second file
|
||||
(*scan_options).clear();
|
||||
key_ranges[0] = "key000010";
|
||||
key_ranges[1] = "key000020";
|
||||
key_ranges[2] = "key0000500";
|
||||
key_ranges[3] = "key000060";
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
int i = 0;
|
||||
int count = 0;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[i]);
|
||||
ASSERT_LT(it.first.ToString(), key_ranges[i + 1]);
|
||||
auto kiter = kvs.find(it.first.ToString());
|
||||
ASSERT_NE(kiter, kvs.end());
|
||||
ASSERT_EQ(kiter->second, it.second.ToString());
|
||||
count++;
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
ASSERT_EQ(i, 4);
|
||||
ASSERT_EQ(count, 19);
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_OK(ex.status());
|
||||
}
|
||||
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();
|
||||
ASSERT_EQ(i, 90);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -339,135 +339,6 @@ TEST_F(DBMemTableTest, ColumnFamilyId) {
|
||||
}
|
||||
}
|
||||
|
||||
class DBMemTableTestForSeek : public DBMemTableTest,
|
||||
virtual public ::testing::WithParamInterface<
|
||||
std::tuple<bool, bool, bool>> {};
|
||||
|
||||
TEST_P(DBMemTableTestForSeek, IntegrityChecks) {
|
||||
// Validate key corruption could be detected during seek.
|
||||
// We insert many keys into skiplist. Then we corrupt the each key one at a
|
||||
// time. With memtable_veirfy_per_key_checksum_on_seek enabled, when the
|
||||
// corrupted key is searched, the checksum of every key visited during the
|
||||
// seek is validated. It will report data corruption. Otherwise seek returns
|
||||
// not found.
|
||||
auto allow_data_in_error = std::get<0>(GetParam());
|
||||
Options options = CurrentOptions();
|
||||
options.allow_data_in_errors = allow_data_in_error;
|
||||
options.paranoid_memory_checks = std::get<1>(GetParam());
|
||||
options.memtable_veirfy_per_key_checksum_on_seek = std::get<2>(GetParam());
|
||||
options.memtable_protection_bytes_per_key = 8;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// capture the data pointer of all of the keys
|
||||
std::vector<char*> raw_data_pointer;
|
||||
|
||||
// Insert enough keys, so memtable would create multiple levels.
|
||||
auto key_count = 100;
|
||||
for (int i = 0; i < key_count; i++) {
|
||||
// The last digit of the key will be corrupted from value 0 to value 5
|
||||
ASSERT_OK(Put(Key(i * 10), "val0"));
|
||||
}
|
||||
|
||||
ReadOptions rops;
|
||||
|
||||
// Iterate all the keys to get key pointers
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack("InlineSkipList::Iterator::Next::key",
|
||||
[&raw_data_pointer](void* key) {
|
||||
auto p = static_cast<char*>(key);
|
||||
raw_data_pointer.push_back(p);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator(rops)};
|
||||
iter->Seek(Key(0));
|
||||
while (iter->Valid()) {
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Next();
|
||||
}
|
||||
// check status after valid returned false.
|
||||
auto status = iter->status();
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_EQ(raw_data_pointer.size(), key_count);
|
||||
|
||||
bool enable_key_validation_on_seek =
|
||||
options.memtable_veirfy_per_key_checksum_on_seek;
|
||||
|
||||
// For each key, corrupt it, validate corruption is detected correctly, then
|
||||
// revert it.
|
||||
for (int i = 0; i < key_count; i++) {
|
||||
std::string key_to_corrupt = Key(i * 10);
|
||||
raw_data_pointer[i][key_to_corrupt.size()] = '5';
|
||||
|
||||
auto corrupted_key = key_to_corrupt;
|
||||
corrupted_key.data()[key_to_corrupt.size() - 1] = '5';
|
||||
auto corrupted_key_slice =
|
||||
Slice(corrupted_key.data(), corrupted_key.length());
|
||||
auto corrupted_key_hex = corrupted_key_slice.ToString(/*hex=*/true);
|
||||
|
||||
{
|
||||
// Test Get API
|
||||
std::string val;
|
||||
auto status = db_->Get(rops, key_to_corrupt, &val);
|
||||
if (enable_key_validation_on_seek) {
|
||||
ASSERT_TRUE(status.IsCorruption()) << key_to_corrupt;
|
||||
ASSERT_EQ(
|
||||
status.ToString().find(corrupted_key_hex) != std::string::npos,
|
||||
allow_data_in_error)
|
||||
<< status.ToString() << "\n"
|
||||
<< corrupted_key_hex;
|
||||
} else {
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Test MultiGet API
|
||||
std::vector<std::string> vals;
|
||||
std::vector<Status> statuses = db_->MultiGet(
|
||||
rops, {db_->DefaultColumnFamily()}, {key_to_corrupt}, &vals, nullptr);
|
||||
if (enable_key_validation_on_seek) {
|
||||
ASSERT_TRUE(statuses[0].IsCorruption());
|
||||
ASSERT_EQ(
|
||||
statuses[0].ToString().find(corrupted_key_hex) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
} else {
|
||||
ASSERT_TRUE(statuses[0].IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Test Iterator Seek API
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator(rops)};
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Seek(key_to_corrupt);
|
||||
auto status = iter->status();
|
||||
if (enable_key_validation_on_seek) {
|
||||
ASSERT_TRUE(status.IsCorruption());
|
||||
ASSERT_EQ(
|
||||
status.ToString().find(corrupted_key_hex) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
} else {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_FALSE(status.ok());
|
||||
}
|
||||
}
|
||||
|
||||
// revert the key corruption.
|
||||
raw_data_pointer[i][key_to_corrupt.size()] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBMemTableTestForSeek, DBMemTableTestForSeek,
|
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool()));
|
||||
|
||||
TEST_F(DBMemTableTest, IntegrityChecks) {
|
||||
// We insert keys key000000, key000001 and key000002 into skiplist at fixed
|
||||
// height 1 (smallest height). Then we corrupt the second key to aey000001 to
|
||||
|
||||
@@ -432,47 +432,12 @@ TEST_F(DBOptionsTest, SetWalBytesPerSync) {
|
||||
ASSERT_GT(low_bytes_per_sync, counter);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, MutableManifestOptions) {
|
||||
// These aren't end-to-end tests, but sufficient to ensure the VersionSet
|
||||
// receives the updates with SetDBOptions
|
||||
for (int64_t i : {0, 1, 100, 100000, 10000000}) {
|
||||
ASSERT_OK(
|
||||
db_->SetDBOptions({{"max_manifest_file_size", std::to_string(i)}}));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(db_->GetDBOptions().max_manifest_file_size));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetMinMaxManifestFileSize()));
|
||||
if (i > 1) {
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(
|
||||
db_->SetDBOptions({{"max_manifest_space_amp_pct", std::to_string(i)}}));
|
||||
ASSERT_EQ(i, static_cast<int64_t>(
|
||||
db_->GetDBOptions().max_manifest_space_amp_pct));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetMaxManifestSpaceAmpPct()));
|
||||
if (i > 1) {
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(db_->SetDBOptions(
|
||||
{{"manifest_preallocation_size", std::to_string(i)}}));
|
||||
ASSERT_EQ(i, static_cast<int64_t>(
|
||||
db_->GetDBOptions().manifest_preallocation_size));
|
||||
ASSERT_EQ(
|
||||
i, static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetManifestPreallocationSize()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, WritableFileMaxBufferSize) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 1024 * 1024;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.env = env_;
|
||||
int buffer_size = 1024 * 1024;
|
||||
Reopen(options);
|
||||
|
||||
@@ -3825,89 +3825,6 @@ 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) {
|
||||
|
||||
@@ -442,107 +442,6 @@ TEST_P(DBRateLimiterOnWriteWALTest, AutoWalFlush) {
|
||||
EXPECT_EQ(actual_auto_wal_flush_request,
|
||||
options_.rate_limiter->GetTotalRequests(Env::IO_USER));
|
||||
}
|
||||
|
||||
class DBRateLimiterOnManualWALFlushTest
|
||||
: public DBRateLimiterOnWriteTest,
|
||||
public ::testing::WithParamInterface<Env::IOPriority> {
|
||||
public:
|
||||
static std::string GetTestNameSuffix(
|
||||
::testing::TestParamInfo<Env::IOPriority> info) {
|
||||
std::ostringstream oss;
|
||||
if (info.param == Env::IO_USER) {
|
||||
oss << "RateLimitManualWALFlush";
|
||||
} else if (info.param == Env::IO_TOTAL) {
|
||||
oss << "NoRateLimitManualWALFlush";
|
||||
} else if (info.param == Env::IO_HIGH) {
|
||||
oss << "RateLimitManualWALFlushWithHighPriority";
|
||||
} else {
|
||||
oss << "RateLimitManualWALFlushWithLowPriority";
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
explicit DBRateLimiterOnManualWALFlushTest()
|
||||
: rate_limiter_priority_(GetParam()) {}
|
||||
|
||||
void Init() {
|
||||
options_ = GetOptions();
|
||||
// Enable manual WAL flush mode
|
||||
options_.manual_wal_flush = true;
|
||||
Reopen(options_);
|
||||
}
|
||||
|
||||
WriteOptions GetWriteOptions() {
|
||||
WriteOptions write_options;
|
||||
// WAL must be enabled for manual WAL flush to work
|
||||
write_options.disableWAL = false;
|
||||
// In manual WAL flush mode, WAL write rate limiting should be done through
|
||||
// FlushWAL(), not WriteOptions::rate_limiter_priority
|
||||
write_options.rate_limiter_priority = Env::IO_TOTAL;
|
||||
return write_options;
|
||||
}
|
||||
|
||||
protected:
|
||||
Env::IOPriority rate_limiter_priority_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBRateLimiterOnManualWALFlushTest,
|
||||
DBRateLimiterOnManualWALFlushTest,
|
||||
::testing::Values(Env::IO_TOTAL, Env::IO_USER,
|
||||
Env::IO_HIGH, Env::IO_LOW),
|
||||
DBRateLimiterOnManualWALFlushTest::GetTestNameSuffix);
|
||||
|
||||
TEST_P(DBRateLimiterOnManualWALFlushTest, ManualWALFlush) {
|
||||
Init();
|
||||
|
||||
const bool no_rate_limit = (rate_limiter_priority_ == Env::IO_TOTAL);
|
||||
|
||||
ASSERT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL));
|
||||
|
||||
for (bool sync : {false, true}) {
|
||||
std::int64_t prev_total_request =
|
||||
options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL);
|
||||
|
||||
Status put_status = Put("key_" + std::to_string(sync),
|
||||
"value_" + std::to_string(sync), GetWriteOptions());
|
||||
|
||||
EXPECT_TRUE(put_status.ok());
|
||||
|
||||
// Since manual_wal_flush is enabled and write_options.rate_limiter_priority
|
||||
// is IO_TOTAL, no rate limiting should have occurred for this user write
|
||||
EXPECT_EQ(0, options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) -
|
||||
prev_total_request);
|
||||
|
||||
// Now explicitly flush the WAL with the test's rate_limiter_priority
|
||||
prev_total_request = options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL);
|
||||
std::int64_t prev_priority_request =
|
||||
options_.rate_limiter->GetTotalRequests(rate_limiter_priority_);
|
||||
|
||||
FlushWALOptions flush_options;
|
||||
flush_options.sync = sync;
|
||||
flush_options.rate_limiter_priority = rate_limiter_priority_;
|
||||
Status flush_status = db_->FlushWAL(flush_options);
|
||||
|
||||
EXPECT_TRUE(flush_status.ok());
|
||||
|
||||
std::int64_t manual_wal_flush_requests_total =
|
||||
options_.rate_limiter->GetTotalRequests(Env::IO_TOTAL) -
|
||||
prev_total_request;
|
||||
std::int64_t manual_wal_flush_requests_for_priority =
|
||||
options_.rate_limiter->GetTotalRequests(rate_limiter_priority_) -
|
||||
prev_priority_request;
|
||||
|
||||
if (no_rate_limit) {
|
||||
EXPECT_EQ(0, manual_wal_flush_requests_total);
|
||||
EXPECT_EQ(0, manual_wal_flush_requests_for_priority);
|
||||
} else {
|
||||
EXPECT_EQ(manual_wal_flush_requests_total,
|
||||
manual_wal_flush_requests_for_priority);
|
||||
EXPECT_GT(manual_wal_flush_requests_for_priority, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -787,46 +787,6 @@ TEST_P(DBTablePropertiesTest, RatioBasedDeletionTriggeredCompactionMarking) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, KeyLargestSmallestSeqno) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key1", "value1"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key2", "value2"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key3", "value3"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
{
|
||||
TablePropertiesCollection props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
|
||||
ASSERT_EQ(1U, props.size());
|
||||
|
||||
auto table_props = props.begin()->second;
|
||||
|
||||
ASSERT_TRUE(table_props->HasKeyLargestSeqno());
|
||||
ASSERT_TRUE(table_props->HasKeySmallestSeqno());
|
||||
|
||||
ASSERT_EQ(table_props->key_largest_seqno,
|
||||
table_props->key_smallest_seqno + 2);
|
||||
ASSERT_GT(table_props->key_largest_seqno, 0U);
|
||||
ASSERT_GT(table_props->key_smallest_seqno, 0U);
|
||||
}
|
||||
|
||||
// Becomes zero after compaction
|
||||
{
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
TablePropertiesCollection props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
|
||||
ASSERT_EQ(1U, props.size());
|
||||
|
||||
auto table_props = props.begin()->second;
|
||||
ASSERT_TRUE(table_props->HasKeyLargestSeqno());
|
||||
ASSERT_TRUE(table_props->HasKeySmallestSeqno());
|
||||
|
||||
ASSERT_EQ(table_props->key_largest_seqno, table_props->key_smallest_seqno);
|
||||
ASSERT_EQ(table_props->key_largest_seqno, 0U);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBTablePropertiesTest, DBTablePropertiesTest,
|
||||
::testing::Values("kCompactionStyleLevel",
|
||||
"kCompactionStyleUniversal"));
|
||||
|
||||
+4
-249
@@ -1492,246 +1492,6 @@ 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;
|
||||
@@ -3775,11 +3535,6 @@ 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();
|
||||
}
|
||||
@@ -5179,7 +4934,7 @@ TEST_F(DBTest, DynamicMemtableOptions) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
namespace {
|
||||
bool VerifyOperationCount(Env* env, ThreadStatus::OperationType op_type,
|
||||
int expected_count) {
|
||||
@@ -5637,7 +5392,7 @@ TEST_P(DBTestWithParam, PreShutdownCompactionMiddle) {
|
||||
ASSERT_EQ(operation_count[ThreadStatus::OP_COMPACTION], 0);
|
||||
}
|
||||
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
|
||||
TEST_F(DBTest, FlushOnDestroy) {
|
||||
WriteOptions wo;
|
||||
@@ -6372,9 +6127,9 @@ TEST_F(DBTest, MergeTestTime) {
|
||||
|
||||
ASSERT_EQ(1, count);
|
||||
ASSERT_EQ(4000000, TestGetTickerCount(options, MERGE_OPERATION_TOTAL_TIME));
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
ASSERT_GT(TestGetTickerCount(options, FLUSH_WRITE_BYTES), 0);
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
}
|
||||
|
||||
TEST_P(DBTestWithParam, MergeCompactionTimeTest) {
|
||||
|
||||
+15
-12
@@ -5205,7 +5205,6 @@ 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());
|
||||
@@ -5897,7 +5896,6 @@ 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"));
|
||||
@@ -5917,7 +5915,6 @@ 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"));
|
||||
@@ -6066,9 +6063,15 @@ TEST_F(DBTest2, VariousFileTemperatures) {
|
||||
};
|
||||
|
||||
// We don't have enough non-unknown temps to confidently distinguish that
|
||||
// a specific setting caused a specific outcome, in a single run. Using
|
||||
// RandomKnownTemperature() is a reasonable work-around without blowing up
|
||||
// test time.
|
||||
// a specific setting caused a specific outcome, in a single run. This is a
|
||||
// reasonable work-around without blowing up test time. Only returns
|
||||
// non-unknown temperatures.
|
||||
auto RandomTemp = [] {
|
||||
static std::vector<Temperature> temps = {
|
||||
Temperature::kHot, Temperature::kWarm, Temperature::kCold};
|
||||
return temps[Random::GetTLSInstance()->Uniform(
|
||||
static_cast<int>(temps.size()))];
|
||||
};
|
||||
|
||||
auto test_fs = std::make_shared<MyTestFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, test_fs));
|
||||
@@ -6084,22 +6087,22 @@ TEST_F(DBTest2, VariousFileTemperatures) {
|
||||
options.env = env.get();
|
||||
test_fs->Reset();
|
||||
if (use_optimize) {
|
||||
test_fs->optimize_manifest_temperature = RandomKnownTemperature();
|
||||
test_fs->optimize_manifest_temperature = RandomTemp();
|
||||
test_fs->expected_manifest_temperature =
|
||||
test_fs->optimize_manifest_temperature;
|
||||
test_fs->optimize_wal_temperature = RandomKnownTemperature();
|
||||
test_fs->optimize_wal_temperature = RandomTemp();
|
||||
test_fs->expected_wal_temperature = test_fs->optimize_wal_temperature;
|
||||
}
|
||||
if (use_temp_options) {
|
||||
options.metadata_write_temperature = RandomKnownTemperature();
|
||||
options.metadata_write_temperature = RandomTemp();
|
||||
test_fs->expected_manifest_temperature =
|
||||
options.metadata_write_temperature;
|
||||
test_fs->expected_other_metadata_temperature =
|
||||
options.metadata_write_temperature;
|
||||
options.wal_write_temperature = RandomKnownTemperature();
|
||||
options.wal_write_temperature = RandomTemp();
|
||||
test_fs->expected_wal_temperature = options.wal_write_temperature;
|
||||
options.last_level_temperature = RandomKnownTemperature();
|
||||
options.default_write_temperature = RandomKnownTemperature();
|
||||
options.last_level_temperature = RandomTemp();
|
||||
options.default_write_temperature = RandomTemp();
|
||||
}
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
+3
-13
@@ -71,9 +71,9 @@ DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
|
||||
if (getenv("MEM_ENV")) {
|
||||
mem_env_ = MockEnv::Create(base_env, base_env->GetSystemClock());
|
||||
}
|
||||
if (auto ee = getenv("ENCRYPTED_ENV")) {
|
||||
if (getenv("ENCRYPTED_ENV")) {
|
||||
std::shared_ptr<EncryptionProvider> provider;
|
||||
std::string provider_id = ee;
|
||||
std::string provider_id = getenv("ENCRYPTED_ENV");
|
||||
if (provider_id.find('=') == std::string::npos &&
|
||||
!EndsWith(provider_id, "://test")) {
|
||||
provider_id = provider_id + "://test";
|
||||
@@ -454,8 +454,7 @@ Options DBTestBase::GetOptions(
|
||||
options.allow_mmap_reads = can_allow_mmap;
|
||||
break;
|
||||
case kManifestFileSize:
|
||||
options.max_manifest_file_size = 50; // 50 bytes
|
||||
options.max_manifest_space_amp_pct = 0; // old behavior
|
||||
options.max_manifest_file_size = 50; // 50 bytes
|
||||
break;
|
||||
case kPerfOptions:
|
||||
options.delayed_write_rate = 8 * 1024 * 1024;
|
||||
@@ -1873,13 +1872,4 @@ template class TargetCacheChargeTrackingCache<
|
||||
CacheEntryRole::kBlockBasedTableReader>;
|
||||
template class TargetCacheChargeTrackingCache<CacheEntryRole::kFileMetadata>;
|
||||
|
||||
const std::vector<Temperature> kKnownTemperatures = {
|
||||
Temperature::kHot, Temperature::kWarm, Temperature::kCool,
|
||||
Temperature::kCold, Temperature::kIce};
|
||||
|
||||
Temperature RandomKnownTemperature() {
|
||||
return kKnownTemperatures[Random::GetTLSInstance()->Uniform(
|
||||
static_cast<int>(kKnownTemperatures.size()))];
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+14
-21
@@ -1438,23 +1438,20 @@ class DBTestBase : public testing::Test {
|
||||
std::replace(tp_string.begin(), tp_string.end(), ';', ' ');
|
||||
std::replace(tp_string.begin(), tp_string.end(), '=', ' ');
|
||||
ResetTableProperties(tp);
|
||||
int count = sscanf(
|
||||
tp_string.c_str(),
|
||||
"# data blocks %" SCNu64 " # entries %" SCNu64 " # deletions %" SCNu64
|
||||
" # merge operands %" SCNu64 " # range deletions %" SCNu64
|
||||
" raw key size %" SCNu64
|
||||
" raw average key size %lf "
|
||||
" raw value size %" SCNu64
|
||||
" raw average value size %lf "
|
||||
" data block size %" SCNu64 " data uncompressed size %" SCNu64
|
||||
" index block size (user-key? %" SCNu64 ", delta-value? %" SCNu64
|
||||
") %" SCNu64 " filter block size %" SCNu64,
|
||||
&tp->num_data_blocks, &tp->num_entries, &tp->num_deletions,
|
||||
&tp->num_merge_operands, &tp->num_range_deletions, &tp->raw_key_size,
|
||||
&dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size,
|
||||
&tp->uncompressed_data_size, &tp->index_key_is_user_key,
|
||||
&tp->index_value_is_delta_encoded, &tp->index_size, &tp->filter_size);
|
||||
ASSERT_EQ(count, 15);
|
||||
sscanf(tp_string.c_str(),
|
||||
"# data blocks %" SCNu64 " # entries %" SCNu64
|
||||
" # deletions %" SCNu64 " # merge operands %" SCNu64
|
||||
" # range deletions %" SCNu64 " raw key size %" SCNu64
|
||||
" raw average key size %lf "
|
||||
" raw value size %" SCNu64
|
||||
" raw average value size %lf "
|
||||
" data block size %" SCNu64 " index block size (user-key? %" SCNu64
|
||||
", delta-value? %" SCNu64 ") %" SCNu64 " filter block size %" SCNu64,
|
||||
&tp->num_data_blocks, &tp->num_entries, &tp->num_deletions,
|
||||
&tp->num_merge_operands, &tp->num_range_deletions, &tp->raw_key_size,
|
||||
&dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size,
|
||||
&tp->index_key_is_user_key, &tp->index_value_is_delta_encoded,
|
||||
&tp->index_size, &tp->filter_size);
|
||||
}
|
||||
|
||||
private: // Prone to error on direct use
|
||||
@@ -1467,8 +1464,4 @@ class DBTestBase : public testing::Test {
|
||||
// unique ids.
|
||||
void VerifySstUniqueIds(const TablePropertiesCollection& props);
|
||||
|
||||
// Excludes kUnknown
|
||||
extern const std::vector<Temperature> kKnownTemperatures;
|
||||
Temperature RandomKnownTemperature();
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+2
-3
@@ -1746,8 +1746,8 @@ class RecoveryTestHelper {
|
||||
WriteController write_controller;
|
||||
|
||||
versions.reset(new VersionSet(
|
||||
test->dbname_, &db_options, MutableDBOptions{options}, file_options,
|
||||
table_cache.get(), &write_buffer_manager, &write_controller,
|
||||
test->dbname_, &db_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,7 +2277,6 @@ 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);
|
||||
|
||||
|
||||
@@ -333,50 +333,6 @@ TEST_F(FormatTest, ReplaceInternalKeyWithMinTimestamp) {
|
||||
ASSERT_EQ(kTypeValue, new_key.type);
|
||||
}
|
||||
|
||||
TEST(RocksdbVersionTest, Version) {
|
||||
// Test preprocessor macros for versioning
|
||||
ASSERT_GT(ROCKSDB_MAJOR, 0);
|
||||
ASSERT_GE(ROCKSDB_MINOR, 0);
|
||||
ASSERT_GE(ROCKSDB_PATCH, 0);
|
||||
ASSERT_LT(ROCKSDB_MAJOR, 1000);
|
||||
ASSERT_LT(ROCKSDB_MINOR, 1000);
|
||||
ASSERT_LT(ROCKSDB_PATCH, 1000);
|
||||
ASSERT_EQ(ROCKSDB_MAKE_VERSION_INT(123, 456, 789), 123456789);
|
||||
ASSERT_GT(ROCKSDB_VERSION_INT, 9999999);
|
||||
ASSERT_LT(ROCKSDB_VERSION_INT, 99999999);
|
||||
static_assert(ROCKSDB_VERSION_GE(9, 8, 7));
|
||||
static_assert(
|
||||
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH));
|
||||
static_assert(
|
||||
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH - 1));
|
||||
static_assert(
|
||||
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH - 100));
|
||||
static_assert(
|
||||
ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR - 1, ROCKSDB_PATCH + 1));
|
||||
static_assert(ROCKSDB_VERSION_GE(ROCKSDB_MAJOR - 1, ROCKSDB_MINOR + 1,
|
||||
ROCKSDB_PATCH + 1));
|
||||
static_assert(
|
||||
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 1));
|
||||
static_assert(
|
||||
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 100));
|
||||
static_assert(
|
||||
!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR + 1, ROCKSDB_PATCH - 1));
|
||||
static_assert(!ROCKSDB_VERSION_GE(ROCKSDB_MAJOR + 1, ROCKSDB_MINOR - 1,
|
||||
ROCKSDB_PATCH - 1));
|
||||
// More typical usage (but with literal numbers based on relevant API
|
||||
// features)
|
||||
#if ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH)
|
||||
static_assert(true);
|
||||
#else
|
||||
static_assert(false);
|
||||
#endif
|
||||
#if !ROCKSDB_VERSION_GE(ROCKSDB_MAJOR, ROCKSDB_MINOR, ROCKSDB_PATCH + 1)
|
||||
static_assert(true);
|
||||
#else
|
||||
static_assert(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -129,7 +129,6 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< "user_defined_timestamps_persisted"
|
||||
<< table_properties.user_defined_timestamps_persisted
|
||||
<< "key_largest_seqno" << table_properties.key_largest_seqno
|
||||
<< "key_smallest_seqno" << table_properties.key_smallest_seqno
|
||||
<< "merge_operator" << table_properties.merge_operator_name
|
||||
<< "prefix_extractor_name"
|
||||
<< table_properties.prefix_extractor_name << "property_collectors"
|
||||
|
||||
@@ -42,9 +42,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
status =
|
||||
GetIngestedFileInfo(file_path, next_file_number++, &file_to_ingest, sv);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to get ingested file info: %s: %s",
|
||||
file_path.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -201,10 +198,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"Tried to link file %s but it's not supported : %s",
|
||||
path_outside_db.c_str(), status.ToString().c_str());
|
||||
} else {
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Failed to link file %s to %s: %s",
|
||||
path_outside_db.c_str(), path_inside_db.c_str(),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
} else {
|
||||
f.copy_file = true;
|
||||
@@ -229,12 +222,6 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
io_tracer_);
|
||||
// The destination of the copy will be ingested
|
||||
f.file_temperature = dst_temp;
|
||||
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Failed to copy file %s to %s: %s",
|
||||
path_outside_db.c_str(), path_inside_db.c_str(),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
} else {
|
||||
// Note: we currently assume that linking files does not cross
|
||||
// temperatures, so no need to change f.file_temperature
|
||||
@@ -460,11 +447,6 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
}
|
||||
status = cfd_->RangesOverlapWithMemtables(
|
||||
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to check ranges overlap with memtables: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
if (status.ok() && *flush_needed) {
|
||||
if (!ingestion_options_.allow_blocking_flush) {
|
||||
@@ -499,9 +481,6 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
bool need_flush = false;
|
||||
status = NeedsFlush(&need_flush, super_version);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to check if flush is needed: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
if (need_flush) {
|
||||
@@ -573,9 +552,6 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
&last_seqno, &batch_uppermost_level,
|
||||
prev_batch_uppermost_level);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to assign levels for one batch: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -618,8 +594,6 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
&largest_parsed, false /* log_err_key */);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Failed to parse internal key: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -642,10 +616,6 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
|
||||
status = AssignGlobalSeqnoForIngestedFile(file, assigned_seqno);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to assign global sequence number for ingested file: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run",
|
||||
@@ -658,9 +628,6 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
|
||||
status = GenerateChecksumForIngestedFile(file);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to generate checksum for ingested file: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -749,7 +716,8 @@ void ExternalSstFileIngestionJob::CreateEquivalentFileIngestingCompactions() {
|
||||
cfd_->ioptions().compaction_style),
|
||||
LLONG_MAX /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID, not applicable */, mutable_cf_options.compression,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
0 /* max_subcompaction, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
@@ -885,10 +853,6 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
|
||||
Status status =
|
||||
fs_->NewRandomAccessFile(external_file, fo, &sst_file, nullptr);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to create random access file for external file %s: %s",
|
||||
external_file.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
Temperature updated_temp = sst_file->GetTemperature();
|
||||
@@ -1011,10 +975,6 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
// user_defined_timestamps_persisted flag for the file.
|
||||
file_to_ingest->user_defined_timestamps_persisted = false;
|
||||
} else if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"ValidateUserDefinedTimestampsOptions failed for external file %s: %s",
|
||||
external_file.c_str(), s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -1039,9 +999,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
Status status = fs_->GetFileSize(external_file, IOOptions(),
|
||||
&file_to_ingest->file_size, nullptr);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to get file size for external file %s: %s",
|
||||
external_file.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1058,19 +1015,12 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
/*user_defined_timestamps_persisted=*/true, sv,
|
||||
file_to_ingest, &table_reader);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to reset table reader for external file %s: %s",
|
||||
external_file.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
status = SanityCheckTableProperties(external_file, new_file_number, sv,
|
||||
file_to_ingest, &table_reader);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to sanity check table properties for external file %s: %s",
|
||||
external_file.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1084,10 +1034,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
table_reader.get(), sv, file_to_ingest, allow_data_in_errors);
|
||||
|
||||
if (!seqno_status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to get sequence number boundary for external file %s: %s",
|
||||
external_file.c_str(), seqno_status.ToString().c_str());
|
||||
return seqno_status;
|
||||
}
|
||||
assert(file_to_ingest->smallest_seqno <= file_to_ingest->largest_seqno);
|
||||
@@ -1115,9 +1061,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
status = table_reader->VerifyChecksum(
|
||||
ro, TableReaderCaller::kExternalSSTIngestion);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to verify checksum for table reader: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -1309,9 +1252,6 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
ro, env_options_, file_to_ingest->start_ukey,
|
||||
file_to_ingest->limit_ukey, lvl, &overlap_with_level);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to check overlap with level iterator: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
if (overlap_with_level) {
|
||||
@@ -1424,14 +1364,6 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
PutFixed64(&seqno_val, seqno);
|
||||
status = fsptr->Write(file_to_ingest->global_seqno_offset, seqno_val,
|
||||
IOOptions(), nullptr);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to write global seqno to %s: %s",
|
||||
file_to_ingest->internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno");
|
||||
status = SyncIngestedFile(fsptr.get());
|
||||
@@ -1448,11 +1380,6 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
return status;
|
||||
}
|
||||
} else if (!status.IsNotSupported()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to open ingested file %s for random read/write: %s",
|
||||
file_to_ingest->internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -1485,9 +1412,6 @@ IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
|
||||
db_options_.allow_mmap_reads, io_tracer_, db_options_.rate_limiter.get(),
|
||||
ro, db_options_.stats, db_options_.clock);
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "Failed to generate checksum for %s: %s",
|
||||
file_to_ingest->internal_file_path.c_str(), io_s.ToString().c_str());
|
||||
return io_s;
|
||||
}
|
||||
file_to_ingest->file_checksum = std::move(file_checksum);
|
||||
@@ -1530,26 +1454,21 @@ Status ExternalSstFileIngestionJob::SyncIngestedFile(TWritableFile* file) {
|
||||
Status ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile(
|
||||
TableReader* table_reader, SuperVersion* sv,
|
||||
IngestedFileInfo* file_to_ingest, bool allow_data_in_errors) {
|
||||
const auto tp = table_reader->GetTableProperties();
|
||||
const bool has_largest_seqno = tp->HasKeyLargestSeqno();
|
||||
SequenceNumber largest_seqno = tp->key_largest_seqno;
|
||||
if (has_largest_seqno) {
|
||||
file_to_ingest->largest_seqno = largest_seqno;
|
||||
if (largest_seqno == 0) {
|
||||
file_to_ingest->smallest_seqno = 0;
|
||||
return Status::OK();
|
||||
}
|
||||
if (tp->HasKeySmallestSeqno()) {
|
||||
file_to_ingest->smallest_seqno = tp->key_smallest_seqno;
|
||||
return Status::OK();
|
||||
}
|
||||
const bool has_largest_seqno =
|
||||
table_reader->GetTableProperties()->HasKeyLargestSeqno();
|
||||
SequenceNumber largest_seqno =
|
||||
table_reader->GetTableProperties()->key_largest_seqno;
|
||||
if (has_largest_seqno && largest_seqno == 0) {
|
||||
file_to_ingest->largest_seqno = 0;
|
||||
file_to_ingest->smallest_seqno = 0;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// For older SST files they may not be recorded in table properties, so
|
||||
// we scan the file to find out.
|
||||
TEST_SYNC_POINT(
|
||||
"ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile:FileScan");
|
||||
// The following file scan is only executed when ingesting files with
|
||||
// non-zero seqno.
|
||||
// TODO: record smallest_seqno in table properties to avoid the
|
||||
// file scan here.
|
||||
SequenceNumber smallest_seqno = kMaxSequenceNumber;
|
||||
|
||||
SequenceNumber largest_seqno_from_iter = 0;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
|
||||
@@ -4094,23 +4094,11 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
|
||||
ingest_opts.fail_if_not_bottommost_level = std::get<3>(GetParam());
|
||||
ingest_opts.link_files = std::get<4>(GetParam());
|
||||
Random* rnd = Random::GetTLSInstance();
|
||||
rnd->Reset(std::random_device{}());
|
||||
std::ostringstream ingest_opts_trace;
|
||||
ingest_opts_trace << "ingest_opts params: " << "snapshot_consistency="
|
||||
<< ingest_opts.snapshot_consistency << ", "
|
||||
<< "allow_global_seqno=" << ingest_opts.allow_global_seqno
|
||||
<< ", " << "allow_blocking_flush="
|
||||
<< ingest_opts.allow_blocking_flush << ", "
|
||||
<< "fail_if_not_bottommost_level="
|
||||
<< ingest_opts.fail_if_not_bottommost_level << ", "
|
||||
<< "link_files=" << ingest_opts.link_files;
|
||||
SCOPED_TRACE(ingest_opts_trace.str());
|
||||
|
||||
do {
|
||||
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.allow_concurrent_memtable_write =
|
||||
false; // Required for VectorRepFactory
|
||||
CreateAndReopenWithCF({"non_overlap", "overlap"}, options);
|
||||
@@ -4147,7 +4135,7 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
|
||||
// optional L5: files in key range [70, 98]
|
||||
// L6: files in key range [1, 79]
|
||||
temp_cf_opts.target_file_size_base =
|
||||
20 << 10; // Small files to create multiple SSTs
|
||||
4 << 10; // Small files to create multiple SSTs
|
||||
temp_cf_opts.num_levels = 7;
|
||||
temp_cf_opts.disable_auto_compactions = true; // Manually set up LSM
|
||||
temp_cf_opts.env = options.env;
|
||||
@@ -4167,7 +4155,7 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
|
||||
const Snapshot* snapshot = from_db->GetSnapshot();
|
||||
|
||||
for (int k = 1; k < 99; ++k) {
|
||||
expected_values[k] = rnd->RandomString(2000);
|
||||
expected_values[k] = rnd->RandomString(500);
|
||||
ASSERT_OK(from_db->Put(wo, temp_cfh, Key(k), expected_values[k]));
|
||||
}
|
||||
ASSERT_OK(from_db->Flush({}, temp_cfh));
|
||||
@@ -4245,21 +4233,9 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
|
||||
"assigned a non-zero sequence number"));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
std::atomic<int> file_scan_count{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile:FileScan",
|
||||
[&](void* /*arg*/) { file_scan_count++; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile(non_overlap_cf, sst_file_paths, ingest_opts));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
EXPECT_EQ(file_scan_count, 0);
|
||||
|
||||
// Validate ingested data.
|
||||
ReadOptions ro;
|
||||
std::string val;
|
||||
@@ -4274,17 +4250,11 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
|
||||
s = db_->IngestExternalFile(overlap_cf, sst_file_paths, ingest_opts);
|
||||
|
||||
ASSERT_NOK(s);
|
||||
if (ingest_opts.fail_if_not_bottommost_level) {
|
||||
ASSERT_TRUE(s.ToString().find("Files cannot be ingested to Lmax") !=
|
||||
std::string::npos)
|
||||
<< s.ToString();
|
||||
} else {
|
||||
ASSERT_TRUE(s.ToString().find("An ingested file overlaps with existing "
|
||||
"data in the DB and has been "
|
||||
"assigned a non-zero sequence number") !=
|
||||
std::string::npos)
|
||||
<< s.ToString();
|
||||
}
|
||||
ASSERT_TRUE(s.ToString().find("An ingested file overlaps with existing "
|
||||
"data in the DB and has been "
|
||||
"assigned a non-zero sequence number") !=
|
||||
std::string::npos)
|
||||
<< s.ToString();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
|
||||
+2
-2
@@ -1104,13 +1104,13 @@ Status FlushJob::WriteLevel0Table() {
|
||||
const uint64_t micros = clock_->NowMicros() - start_micros;
|
||||
const uint64_t cpu_micros = clock_->CPUMicros() - start_cpu_micros;
|
||||
flush_stats.micros = micros;
|
||||
flush_stats.cpu_micros += cpu_micros;
|
||||
flush_stats.cpu_micros = cpu_micros;
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Flush lasted %" PRIu64
|
||||
" microseconds, and %" PRIu64 " cpu microseconds.\n",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, micros,
|
||||
flush_stats.cpu_micros);
|
||||
cpu_micros);
|
||||
|
||||
if (has_output) {
|
||||
flush_stats.bytes_written = meta_.fd.GetFileSize();
|
||||
|
||||
@@ -142,13 +142,13 @@ class FlushJobTestBase : public testing::Test {
|
||||
column_families.emplace_back(cf_name, cf_options_);
|
||||
}
|
||||
|
||||
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));
|
||||
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));
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
}
|
||||
|
||||
|
||||
+17
-7
@@ -163,7 +163,9 @@ 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;
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.table_properties_collector_factories.push_back(
|
||||
std::make_shared<TestPropertiesCollectorFactory>());
|
||||
@@ -227,7 +229,7 @@ class TestFlushListener : public EventListener {
|
||||
ASSERT_EQ(info.file_checksum, kUnknownFileChecksum);
|
||||
ASSERT_EQ(info.file_checksum_func_name, kUnknownFileChecksumFuncName);
|
||||
|
||||
#ifndef NROCKSDB_THREAD_STATUS
|
||||
#ifdef ROCKSDB_USING_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();
|
||||
@@ -244,7 +246,7 @@ class TestFlushListener : public EventListener {
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_match);
|
||||
#endif // !NROCKSDB_THREAD_STATUS
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
}
|
||||
|
||||
void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
|
||||
@@ -308,7 +310,9 @@ TEST_F(EventListenerTest, OnSingleDBFlushTest) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.write_buffer_size = k110KB;
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env, this);
|
||||
options.listeners.emplace_back(listener);
|
||||
std::vector<std::string> cf_names = {"pikachu", "ilya", "muromec",
|
||||
@@ -353,7 +357,9 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.write_buffer_size = k110KB;
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.atomic_flush = atomic_flush;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
@@ -401,7 +407,9 @@ TEST_F(EventListenerTest, MultiCF) {
|
||||
TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
options.table_properties_collector_factories.push_back(
|
||||
std::make_shared<TestPropertiesCollectorFactory>());
|
||||
std::vector<TestFlushListener*> listeners;
|
||||
@@ -489,7 +497,9 @@ TEST_F(EventListenerTest, MultiDBMultiListeners) {
|
||||
TEST_F(EventListenerTest, DisableBGCompaction) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.enable_thread_tracking = ThreadStatus::kEnabled;
|
||||
#ifdef ROCKSDB_USING_THREAD_STATUS
|
||||
options.enable_thread_tracking = true;
|
||||
#endif // ROCKSDB_USING_THREAD_STATUS
|
||||
TestFlushListener* listener = new TestFlushListener(options.env, this);
|
||||
const int kCompactionTrigger = 1;
|
||||
const int kSlowdownTrigger = 5;
|
||||
|
||||
+4
-4
@@ -59,7 +59,7 @@ class Reader {
|
||||
// live while this Reader is in use.
|
||||
//
|
||||
// If "checksum" is true, verify checksums if available.
|
||||
// TODO(hx235): separate WAL related parameters from general `Reader`
|
||||
// TODO(hx235): seperate WAL related parameters from general `Reader`
|
||||
// parameters
|
||||
Reader(std::shared_ptr<Logger> info_log,
|
||||
std::unique_ptr<SequentialFileReader>&& file, Reporter* reporter,
|
||||
@@ -155,7 +155,7 @@ class Reader {
|
||||
// which log number this is
|
||||
uint64_t const log_number_;
|
||||
|
||||
// See `Options::track_and_verify_wals`
|
||||
// See `Optinos::track_and_verify_wals`
|
||||
bool track_and_verify_wals_;
|
||||
// Below variables are used for WAL verification
|
||||
// TODO(hx235): To revise `stop_replay_for_corruption_` inside `LogReader`
|
||||
@@ -208,8 +208,8 @@ class Reader {
|
||||
};
|
||||
|
||||
// Return type, or one of the preceding special values
|
||||
// If WAL compression is enabled, fragment_checksum is the checksum of the
|
||||
// fragment computed from the original buffer containing uncompressed
|
||||
// If WAL compressioned is enabled, fragment_checksum is the checksum of the
|
||||
// fragment computed from the orginal buffer containinng uncompressed
|
||||
// fragment.
|
||||
uint8_t ReadPhysicalRecord(Slice* result, size_t* drop_size,
|
||||
uint64_t* fragment_checksum = nullptr);
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ class Writer {
|
||||
// Create a writer that will append data to "*dest".
|
||||
// "*dest" must be initially empty.
|
||||
// "*dest" must remain live while this Writer is in use.
|
||||
// TODO(hx235): separate WAL related parameters from general `Reader`
|
||||
// TODO(hx235): seperate WAL related parameters from general `Reader`
|
||||
// parameters
|
||||
explicit Writer(std::unique_ptr<WritableFileWriter>&& dest,
|
||||
uint64_t log_number, bool recycle_log_files,
|
||||
|
||||
+13
-38
@@ -70,9 +70,7 @@ ImmutableMemTableOptions::ImmutableMemTableOptions(
|
||||
protection_bytes_per_key(
|
||||
mutable_cf_options.memtable_protection_bytes_per_key),
|
||||
allow_data_in_errors(ioptions.allow_data_in_errors),
|
||||
paranoid_memory_checks(mutable_cf_options.paranoid_memory_checks),
|
||||
memtable_veirfy_per_key_checksum_on_seek(
|
||||
mutable_cf_options.memtable_veirfy_per_key_checksum_on_seek) {}
|
||||
paranoid_memory_checks(mutable_cf_options.paranoid_memory_checks) {}
|
||||
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
const ImmutableOptions& ioptions,
|
||||
@@ -117,13 +115,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
oldest_key_time_(std::numeric_limits<uint64_t>::max()),
|
||||
approximate_memory_usage_(0),
|
||||
memtable_max_range_deletions_(
|
||||
mutable_cf_options.memtable_max_range_deletions),
|
||||
key_validation_callback_(
|
||||
(moptions_.protection_bytes_per_key != 0 &&
|
||||
moptions_.memtable_veirfy_per_key_checksum_on_seek)
|
||||
? std::bind(&MemTable::ValidateKey, this, std::placeholders::_1,
|
||||
std::placeholders::_2)
|
||||
: std::function<Status(const char*, bool)>(nullptr)) {
|
||||
mutable_cf_options.memtable_max_range_deletions) {
|
||||
UpdateFlushState();
|
||||
// something went wrong if we need to flush before inserting anything
|
||||
assert(!ShouldScheduleFlush());
|
||||
@@ -402,11 +394,7 @@ class MemTableIterator : public InternalIterator {
|
||||
!mem.GetImmutableMemTableOptions()->inplace_update_support),
|
||||
arena_mode_(arena != nullptr),
|
||||
paranoid_memory_checks_(mem.moptions_.paranoid_memory_checks),
|
||||
validate_on_seek_(
|
||||
mem.moptions_.paranoid_memory_checks ||
|
||||
mem.moptions_.memtable_veirfy_per_key_checksum_on_seek),
|
||||
allow_data_in_error_(mem.moptions_.allow_data_in_errors),
|
||||
key_validation_callback_(mem.key_validation_callback_) {
|
||||
allow_data_in_error(mem.moptions_.allow_data_in_errors) {
|
||||
if (kind == kRangeDelEntries) {
|
||||
iter_ = mem.range_del_table_->GetIterator(arena);
|
||||
} else if (prefix_extractor_ != nullptr &&
|
||||
@@ -475,10 +463,8 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (validate_on_seek_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error_,
|
||||
paranoid_memory_checks_,
|
||||
key_validation_callback_);
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error);
|
||||
} else {
|
||||
iter_->Seek(k, nullptr);
|
||||
}
|
||||
@@ -502,10 +488,8 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (validate_on_seek_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error_,
|
||||
paranoid_memory_checks_,
|
||||
key_validation_callback_);
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error);
|
||||
} else {
|
||||
iter_->Seek(k, nullptr);
|
||||
}
|
||||
@@ -534,7 +518,7 @@ class MemTableIterator : public InternalIterator {
|
||||
PERF_COUNTER_ADD(next_on_memtable_count, 1);
|
||||
assert(Valid());
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->NextAndValidate(allow_data_in_error_);
|
||||
status_ = iter_->NextAndValidate(allow_data_in_error);
|
||||
} else {
|
||||
iter_->Next();
|
||||
TEST_SYNC_POINT_CALLBACK("MemTableIterator::Next:0", iter_);
|
||||
@@ -556,7 +540,7 @@ class MemTableIterator : public InternalIterator {
|
||||
PERF_COUNTER_ADD(prev_on_memtable_count, 1);
|
||||
assert(Valid());
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->PrevAndValidate(allow_data_in_error_);
|
||||
status_ = iter_->PrevAndValidate(allow_data_in_error);
|
||||
} else {
|
||||
iter_->Prev();
|
||||
}
|
||||
@@ -615,9 +599,7 @@ class MemTableIterator : public InternalIterator {
|
||||
bool value_pinned_;
|
||||
bool arena_mode_;
|
||||
const bool paranoid_memory_checks_;
|
||||
const bool validate_on_seek_;
|
||||
const bool allow_data_in_error_;
|
||||
const std::function<Status(const char*, bool)> key_validation_callback_;
|
||||
const bool allow_data_in_error;
|
||||
|
||||
void VerifyEntryChecksum() {
|
||||
if (protection_bytes_per_key_ > 0 && Valid()) {
|
||||
@@ -1511,13 +1493,11 @@ void MemTable::GetFromTable(const LookupKey& key,
|
||||
saver.allow_data_in_errors = moptions_.allow_data_in_errors;
|
||||
saver.protection_bytes_per_key = moptions_.protection_bytes_per_key;
|
||||
|
||||
if (!moptions_.paranoid_memory_checks &&
|
||||
!moptions_.memtable_veirfy_per_key_checksum_on_seek) {
|
||||
if (!moptions_.paranoid_memory_checks) {
|
||||
table_->Get(key, &saver, SaveValue);
|
||||
} else {
|
||||
Status check_s = table_->GetAndValidate(
|
||||
key, &saver, SaveValue, moptions_.allow_data_in_errors,
|
||||
moptions_.paranoid_memory_checks, key_validation_callback_);
|
||||
Status check_s = table_->GetAndValidate(key, &saver, SaveValue,
|
||||
moptions_.allow_data_in_errors);
|
||||
if (check_s.IsCorruption()) {
|
||||
*(saver.status) = check_s;
|
||||
// Should stop searching the LSM.
|
||||
@@ -1528,11 +1508,6 @@ void MemTable::GetFromTable(const LookupKey& key,
|
||||
*seq = saver.seq;
|
||||
}
|
||||
|
||||
Status MemTable::ValidateKey(const char* key, bool allow_data_in_errors) {
|
||||
return VerifyEntryChecksum(key, moptions_.protection_bytes_per_key,
|
||||
allow_data_in_errors);
|
||||
}
|
||||
|
||||
void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
ReadCallback* callback, bool immutable_memtable) {
|
||||
// The sequence number is updated synchronously in version_set.h
|
||||
|
||||
@@ -64,7 +64,6 @@ struct ImmutableMemTableOptions {
|
||||
uint32_t protection_bytes_per_key;
|
||||
bool allow_data_in_errors;
|
||||
bool paranoid_memory_checks;
|
||||
bool memtable_veirfy_per_key_checksum_on_seek;
|
||||
};
|
||||
|
||||
// Batched counters to updated when inserting keys in one write batch.
|
||||
@@ -827,9 +826,6 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
uint32_t protection_bytes_per_key,
|
||||
bool allow_data_in_errors = false);
|
||||
|
||||
// Validate the checksum of the key/value pair.
|
||||
Status ValidateKey(const char* key, bool allow_data_in_errors);
|
||||
|
||||
private:
|
||||
enum FlushStateEnum { FLUSH_NOT_REQUESTED, FLUSH_REQUESTED, FLUSH_SCHEDULED };
|
||||
|
||||
@@ -960,8 +956,6 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
SequenceNumber s, char* checksum_ptr);
|
||||
|
||||
void MaybeUpdateNewestUDT(const Slice& user_key);
|
||||
|
||||
const std::function<Status(const char*, bool)> key_validation_callback_;
|
||||
};
|
||||
|
||||
const char* EncodeKey(std::string* scratch, const Slice& target);
|
||||
|
||||
@@ -112,8 +112,7 @@ 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,
|
||||
MutableDBOptions{db_options}, env_options,
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller, /*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"",
|
||||
@@ -164,8 +163,7 @@ 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,
|
||||
MutableDBOptions{db_options}, env_options,
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller, /*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"",
|
||||
|
||||
+1
-1
@@ -307,7 +307,7 @@ class MergeOutputIterator {
|
||||
|
||||
Slice key() { return Slice(*it_keys_); }
|
||||
Slice value() { return Slice(*it_values_); }
|
||||
bool Valid() const { return it_keys_ != merge_helper_->keys().rend(); }
|
||||
bool Valid() { return it_keys_ != merge_helper_->keys().rend(); }
|
||||
|
||||
private:
|
||||
const MergeHelper* merge_helper_;
|
||||
|
||||
@@ -32,7 +32,6 @@ bool MergeOperator::FullMergeV3(const MergeOperationInputV3& merge_in,
|
||||
MergeOperationOutputV3* merge_out) const {
|
||||
assert(merge_out);
|
||||
|
||||
Slice value_of_default; // avoid warning about in_v2 pointing at this
|
||||
MergeOperationInput in_v2(merge_in.key, nullptr, merge_in.operand_list,
|
||||
merge_in.logger);
|
||||
|
||||
@@ -67,6 +66,7 @@ bool MergeOperator::FullMergeV3(const MergeOperationInputV3& merge_in,
|
||||
const bool has_default_column =
|
||||
WideColumnsHelper::HasDefaultColumn(existing_columns);
|
||||
|
||||
Slice value_of_default;
|
||||
if (has_default_column) {
|
||||
value_of_default = existing_columns.front().value();
|
||||
}
|
||||
|
||||
@@ -40,11 +40,6 @@ MultiScan::MultiScan(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
MultiScanIterator& MultiScanIterator::operator++() {
|
||||
status_ = db_iter_->status();
|
||||
if (!status_.ok()) {
|
||||
throw MultiScanException(status_);
|
||||
}
|
||||
|
||||
if (idx_ >= scan_opts_.size()) {
|
||||
throw std::logic_error("Index out of range");
|
||||
}
|
||||
|
||||
@@ -303,47 +303,6 @@ TEST_F(ObsoleteFilesTest, BlobFiles) {
|
||||
ASSERT_EQ(deleted_files, expected_deleted_files);
|
||||
}
|
||||
|
||||
TEST_F(ObsoleteFilesTest, GetSortedWalFilesHangsAfterNoopPurge) {
|
||||
// This test used to trigger a hang in `DB::GetSortedWalFiles()`, where it
|
||||
// would wait for a no-op purge that did not signal the CV upon completion.
|
||||
|
||||
// Grab an iterator and flush to switch the super version. That way, when the
|
||||
// iterator is destroyed, it will go through the purge path.
|
||||
DB* db = db_; // Only using `db` makes it clear we only use DB-level APIs.
|
||||
ASSERT_OK(db->Put(WriteOptions(), "key", "value"));
|
||||
std::unique_ptr<Iterator> iter(db->NewIterator(ReadOptions()));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Sync points ensure `GetSortedWalFiles()` waits for a purge after
|
||||
// `FindObsoleteFiles()` releases the mutex but before its corresponding purge
|
||||
// completes.
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"FindObsoleteFiles::PostMutexUnlock", [&](void* /* arg */) {
|
||||
TEST_SYNC_POINT(
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
|
||||
"InCallback:1");
|
||||
TEST_SYNC_POINT(
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:"
|
||||
"InCallback:2");
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:1",
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin"},
|
||||
{"DBImpl::GetSortedWalFilesImpl:WaitPurge",
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:InCallback:2"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
port::Thread get_sorted_wal_files_thread([db]() {
|
||||
TEST_SYNC_POINT(
|
||||
"ObsoleteFilesTest::GetSortedWalFilesHangsAfterNoopPurge:Thread:Begin");
|
||||
VectorWalPtr files;
|
||||
ASSERT_OK(db->GetSortedWalFiles(files));
|
||||
});
|
||||
iter.reset();
|
||||
get_sorted_wal_files_thread.join();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+2
-2
@@ -120,8 +120,8 @@ class Repairer {
|
||||
/*io_tracer=*/nullptr, db_session_id_)),
|
||||
wb_(db_options_.db_write_buffer_size),
|
||||
wc_(db_options_.delayed_write_rate),
|
||||
vset_(dbname_, &immutable_db_options_, MutableDBOptions{db_options_},
|
||||
file_options_, raw_table_cache_.get(), &wb_, &wc_,
|
||||
vset_(dbname_, &immutable_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),
|
||||
|
||||
+140
-478
@@ -112,9 +112,124 @@ bool VersionEdit::EncodeTo(std::string* dst,
|
||||
f.epoch_number == kUnknownEpochNumber) {
|
||||
return false;
|
||||
}
|
||||
EncodeToNewFile4(f, new_files_[i].first, ts_sz.value(),
|
||||
has_min_log_number_to_keep_, min_log_number_to_keep_,
|
||||
min_log_num_written, dst);
|
||||
PutVarint32(dst, kNewFile4);
|
||||
PutVarint32Varint64(dst, new_files_[i].first /* level */, f.fd.GetNumber());
|
||||
PutVarint64(dst, f.fd.GetFileSize());
|
||||
EncodeFileBoundaries(dst, f, ts_sz.value());
|
||||
PutVarint64Varint64(dst, f.fd.smallest_seqno, f.fd.largest_seqno);
|
||||
// Customized fields' format:
|
||||
// +-----------------------------+
|
||||
// | 1st field's tag (varint32) |
|
||||
// +-----------------------------+
|
||||
// | 1st field's size (varint32) |
|
||||
// +-----------------------------+
|
||||
// | bytes for 1st field |
|
||||
// | (based on size decoded) |
|
||||
// +-----------------------------+
|
||||
// | |
|
||||
// | ...... |
|
||||
// | |
|
||||
// +-----------------------------+
|
||||
// | last field's size (varint32)|
|
||||
// +-----------------------------+
|
||||
// | bytes for last field |
|
||||
// | (based on size decoded) |
|
||||
// +-----------------------------+
|
||||
// | terminating tag (varint32) |
|
||||
// +-----------------------------+
|
||||
//
|
||||
// Customized encoding for fields:
|
||||
// tag kPathId: 1 byte as path_id
|
||||
// tag kNeedCompaction:
|
||||
// now only can take one char value 1 indicating need-compaction
|
||||
//
|
||||
PutVarint32(dst, NewFileCustomTag::kOldestAncesterTime);
|
||||
std::string varint_oldest_ancester_time;
|
||||
PutVarint64(&varint_oldest_ancester_time, f.oldest_ancester_time);
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintOldestAncesterTime",
|
||||
&varint_oldest_ancester_time);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_oldest_ancester_time));
|
||||
|
||||
PutVarint32(dst, NewFileCustomTag::kFileCreationTime);
|
||||
std::string varint_file_creation_time;
|
||||
PutVarint64(&varint_file_creation_time, f.file_creation_time);
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintFileCreationTime",
|
||||
&varint_file_creation_time);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_file_creation_time));
|
||||
|
||||
PutVarint32(dst, NewFileCustomTag::kEpochNumber);
|
||||
std::string varint_epoch_number;
|
||||
PutVarint64(&varint_epoch_number, f.epoch_number);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_epoch_number));
|
||||
|
||||
if (f.file_checksum_func_name != kUnknownFileChecksumFuncName) {
|
||||
PutVarint32(dst, NewFileCustomTag::kFileChecksum);
|
||||
PutLengthPrefixedSlice(dst, Slice(f.file_checksum));
|
||||
|
||||
PutVarint32(dst, NewFileCustomTag::kFileChecksumFuncName);
|
||||
PutLengthPrefixedSlice(dst, Slice(f.file_checksum_func_name));
|
||||
}
|
||||
|
||||
if (f.fd.GetPathId() != 0) {
|
||||
PutVarint32(dst, NewFileCustomTag::kPathId);
|
||||
char p = static_cast<char>(f.fd.GetPathId());
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
if (f.temperature != Temperature::kUnknown) {
|
||||
PutVarint32(dst, NewFileCustomTag::kTemperature);
|
||||
char p = static_cast<char>(f.temperature);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
if (f.marked_for_compaction) {
|
||||
PutVarint32(dst, NewFileCustomTag::kNeedCompaction);
|
||||
char p = static_cast<char>(1);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
if (has_min_log_number_to_keep_ && !min_log_num_written) {
|
||||
PutVarint32(dst, NewFileCustomTag::kMinLogNumberToKeepHack);
|
||||
std::string varint_log_number;
|
||||
PutFixed64(&varint_log_number, min_log_number_to_keep_);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_log_number));
|
||||
min_log_num_written = true;
|
||||
}
|
||||
if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
|
||||
PutVarint32(dst, NewFileCustomTag::kOldestBlobFileNumber);
|
||||
std::string oldest_blob_file_number;
|
||||
PutVarint64(&oldest_blob_file_number, f.oldest_blob_file_number);
|
||||
PutLengthPrefixedSlice(dst, Slice(oldest_blob_file_number));
|
||||
}
|
||||
UniqueId64x2 unique_id = f.unique_id;
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:UniqueId", &unique_id);
|
||||
if (unique_id != kNullUniqueId64x2) {
|
||||
PutVarint32(dst, NewFileCustomTag::kUniqueId);
|
||||
std::string unique_id_str = EncodeUniqueIdBytes(&unique_id);
|
||||
PutLengthPrefixedSlice(dst, Slice(unique_id_str));
|
||||
}
|
||||
if (f.compensated_range_deletion_size) {
|
||||
PutVarint32(dst, kCompensatedRangeDeletionSize);
|
||||
std::string compensated_range_deletion_size;
|
||||
PutVarint64(&compensated_range_deletion_size,
|
||||
f.compensated_range_deletion_size);
|
||||
PutLengthPrefixedSlice(dst, Slice(compensated_range_deletion_size));
|
||||
}
|
||||
if (f.tail_size) {
|
||||
PutVarint32(dst, NewFileCustomTag::kTailSize);
|
||||
std::string varint_tail_size;
|
||||
PutVarint64(&varint_tail_size, f.tail_size);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_tail_size));
|
||||
}
|
||||
if (!f.user_defined_timestamps_persisted) {
|
||||
// The default value for the flag is true, it's only explicitly persisted
|
||||
// when it's false. We are putting 0 as the value here to signal false
|
||||
// (i.e. UDTS not persisted).
|
||||
PutVarint32(dst, NewFileCustomTag::kUserDefinedTimestampsPersisted);
|
||||
char p = static_cast<char>(0);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
|
||||
dst);
|
||||
|
||||
PutVarint32(dst, NewFileCustomTag::kTerminate);
|
||||
}
|
||||
|
||||
for (const auto& blob_file_addition : blob_file_additions_) {
|
||||
@@ -173,142 +288,9 @@ bool VersionEdit::EncodeTo(std::string* dst,
|
||||
char p = static_cast<char>(persist_user_defined_timestamps_);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
|
||||
if (HasSubcompactionProgress()) {
|
||||
PutVarint32(dst, kSubcompactionProgress);
|
||||
std::string progress_data;
|
||||
subcompaction_progress_.EncodeTo(&progress_data);
|
||||
PutLengthPrefixedSlice(dst, progress_data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VersionEdit::EncodeToNewFile4(const FileMetaData& f, int level,
|
||||
size_t ts_sz,
|
||||
bool has_min_log_number_to_keep,
|
||||
uint64_t min_log_number_to_keep,
|
||||
bool& min_log_num_written,
|
||||
std::string* dst) {
|
||||
PutVarint32(dst, kNewFile4);
|
||||
PutVarint32Varint64(dst, level, f.fd.GetNumber());
|
||||
PutVarint64(dst, f.fd.GetFileSize());
|
||||
EncodeFileBoundaries(dst, f, ts_sz);
|
||||
PutVarint64Varint64(dst, f.fd.smallest_seqno, f.fd.largest_seqno);
|
||||
// Customized fields' format:
|
||||
// +-----------------------------+
|
||||
// | 1st field's tag (varint32) |
|
||||
// +-----------------------------+
|
||||
// | 1st field's size (varint32) |
|
||||
// +-----------------------------+
|
||||
// | bytes for 1st field |
|
||||
// | (based on size decoded) |
|
||||
// +-----------------------------+
|
||||
// | |
|
||||
// | ...... |
|
||||
// | |
|
||||
// +-----------------------------+
|
||||
// | last field's size (varint32)|
|
||||
// +-----------------------------+
|
||||
// | bytes for last field |
|
||||
// | (based on size decoded) |
|
||||
// +-----------------------------+
|
||||
// | terminating tag (varint32) |
|
||||
// +-----------------------------+
|
||||
//
|
||||
// Customized encoding for fields:
|
||||
// tag kPathId: 1 byte as path_id
|
||||
// tag kNeedCompaction:
|
||||
// now only can take one char value 1 indicating need-compaction
|
||||
//
|
||||
PutVarint32(dst, NewFileCustomTag::kOldestAncesterTime);
|
||||
std::string varint_oldest_ancester_time;
|
||||
PutVarint64(&varint_oldest_ancester_time, f.oldest_ancester_time);
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintOldestAncesterTime",
|
||||
&varint_oldest_ancester_time);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_oldest_ancester_time));
|
||||
|
||||
PutVarint32(dst, NewFileCustomTag::kFileCreationTime);
|
||||
std::string varint_file_creation_time;
|
||||
PutVarint64(&varint_file_creation_time, f.file_creation_time);
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:VarintFileCreationTime",
|
||||
&varint_file_creation_time);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_file_creation_time));
|
||||
|
||||
PutVarint32(dst, NewFileCustomTag::kEpochNumber);
|
||||
std::string varint_epoch_number;
|
||||
PutVarint64(&varint_epoch_number, f.epoch_number);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_epoch_number));
|
||||
|
||||
if (f.file_checksum_func_name != kUnknownFileChecksumFuncName) {
|
||||
PutVarint32(dst, NewFileCustomTag::kFileChecksum);
|
||||
PutLengthPrefixedSlice(dst, Slice(f.file_checksum));
|
||||
|
||||
PutVarint32(dst, NewFileCustomTag::kFileChecksumFuncName);
|
||||
PutLengthPrefixedSlice(dst, Slice(f.file_checksum_func_name));
|
||||
}
|
||||
|
||||
if (f.fd.GetPathId() != 0) {
|
||||
PutVarint32(dst, NewFileCustomTag::kPathId);
|
||||
char p = static_cast<char>(f.fd.GetPathId());
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
if (f.temperature != Temperature::kUnknown) {
|
||||
PutVarint32(dst, NewFileCustomTag::kTemperature);
|
||||
char p = static_cast<char>(f.temperature);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
if (f.marked_for_compaction) {
|
||||
PutVarint32(dst, NewFileCustomTag::kNeedCompaction);
|
||||
char p = static_cast<char>(1);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
if (has_min_log_number_to_keep && !min_log_num_written) {
|
||||
PutVarint32(dst, NewFileCustomTag::kMinLogNumberToKeepHack);
|
||||
std::string varint_log_number;
|
||||
PutFixed64(&varint_log_number, min_log_number_to_keep);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_log_number));
|
||||
min_log_num_written = true;
|
||||
}
|
||||
if (f.oldest_blob_file_number != kInvalidBlobFileNumber) {
|
||||
PutVarint32(dst, NewFileCustomTag::kOldestBlobFileNumber);
|
||||
std::string oldest_blob_file_number;
|
||||
PutVarint64(&oldest_blob_file_number, f.oldest_blob_file_number);
|
||||
PutLengthPrefixedSlice(dst, Slice(oldest_blob_file_number));
|
||||
}
|
||||
UniqueId64x2 unique_id = f.unique_id;
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:UniqueId", &unique_id);
|
||||
if (unique_id != kNullUniqueId64x2) {
|
||||
PutVarint32(dst, NewFileCustomTag::kUniqueId);
|
||||
std::string unique_id_str = EncodeUniqueIdBytes(&unique_id);
|
||||
PutLengthPrefixedSlice(dst, Slice(unique_id_str));
|
||||
}
|
||||
if (f.compensated_range_deletion_size) {
|
||||
PutVarint32(dst, NewFileCustomTag::kCompensatedRangeDeletionSize);
|
||||
std::string compensated_range_deletion_size;
|
||||
PutVarint64(&compensated_range_deletion_size,
|
||||
f.compensated_range_deletion_size);
|
||||
PutLengthPrefixedSlice(dst, Slice(compensated_range_deletion_size));
|
||||
}
|
||||
if (f.tail_size) {
|
||||
PutVarint32(dst, NewFileCustomTag::kTailSize);
|
||||
std::string varint_tail_size;
|
||||
PutVarint64(&varint_tail_size, f.tail_size);
|
||||
PutLengthPrefixedSlice(dst, Slice(varint_tail_size));
|
||||
}
|
||||
if (!f.user_defined_timestamps_persisted) {
|
||||
// The default value for the flag is true, it's only explicitly persisted
|
||||
// when it's false. We are putting 0 as the value here to signal false
|
||||
// (i.e. UDTS not persisted).
|
||||
PutVarint32(dst, NewFileCustomTag::kUserDefinedTimestampsPersisted);
|
||||
char p = static_cast<char>(0);
|
||||
PutLengthPrefixedSlice(dst, Slice(&p, 1));
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("VersionEdit::EncodeTo:NewFile4:CustomizeFields",
|
||||
dst);
|
||||
|
||||
PutVarint32(dst, NewFileCustomTag::kTerminate);
|
||||
}
|
||||
static bool GetInternalKey(Slice* input, InternalKey* dst) {
|
||||
Slice str;
|
||||
if (GetLengthPrefixedSlice(input, &str)) {
|
||||
@@ -319,12 +301,12 @@ static bool GetInternalKey(Slice* input, InternalKey* dst) {
|
||||
}
|
||||
}
|
||||
|
||||
bool VersionEdit::GetLevel(Slice* input, int* level, int& max_level) {
|
||||
bool VersionEdit::GetLevel(Slice* input, int* level, const char** /*msg*/) {
|
||||
uint32_t v = 0;
|
||||
if (GetVarint32(input, &v)) {
|
||||
*level = v;
|
||||
if (max_level < *level) {
|
||||
max_level = *level;
|
||||
if (max_level_ < *level) {
|
||||
max_level_ = *level;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -332,18 +314,16 @@ bool VersionEdit::GetLevel(Slice* input, int* level, int& max_level) {
|
||||
}
|
||||
}
|
||||
|
||||
const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
|
||||
uint64_t& min_log_number_to_keep,
|
||||
bool& has_min_log_number_to_keep,
|
||||
NewFiles& new_files,
|
||||
FileMetaData& f) {
|
||||
const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
const char* msg = nullptr;
|
||||
int level = 0;
|
||||
FileMetaData f;
|
||||
uint64_t number = 0;
|
||||
uint32_t path_id = 0;
|
||||
uint64_t file_size = 0;
|
||||
SequenceNumber smallest_seqno = 0;
|
||||
SequenceNumber largest_seqno = kMaxSequenceNumber;
|
||||
if (GetLevel(input, &level, max_level) && GetVarint64(input, &number) &&
|
||||
if (GetLevel(input, &level, &msg) && GetVarint64(input, &number) &&
|
||||
GetVarint64(input, &file_size) && GetInternalKey(input, &f.smallest) &&
|
||||
GetInternalKey(input, &f.largest) &&
|
||||
GetVarint64(input, &smallest_seqno) &&
|
||||
@@ -401,10 +381,10 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
|
||||
case kMinLogNumberToKeepHack:
|
||||
// This is a hack to encode kMinLogNumberToKeep in a
|
||||
// forward-compatible fashion.
|
||||
if (!GetFixed64(&field, &min_log_number_to_keep)) {
|
||||
if (!GetFixed64(&field, &min_log_number_to_keep_)) {
|
||||
return "deleted log number malformatted";
|
||||
}
|
||||
has_min_log_number_to_keep = true;
|
||||
has_min_log_number_to_keep_ = true;
|
||||
break;
|
||||
case kOldestBlobFileNumber:
|
||||
if (!GetVarint64(&field, &f.oldest_blob_file_number)) {
|
||||
@@ -416,7 +396,7 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
|
||||
return "temperature field wrong size";
|
||||
} else {
|
||||
Temperature casted_field = static_cast<Temperature>(field[0]);
|
||||
if (casted_field < Temperature::kLastTemperature) {
|
||||
if (casted_field <= Temperature::kCold) {
|
||||
f.temperature = casted_field;
|
||||
}
|
||||
}
|
||||
@@ -456,12 +436,13 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input, int& max_level,
|
||||
}
|
||||
f.fd =
|
||||
FileDescriptor(number, path_id, file_size, smallest_seqno, largest_seqno);
|
||||
new_files.emplace_back(level, f);
|
||||
new_files_.push_back(std::make_pair(level, f));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VersionEdit::EncodeFileBoundaries(std::string* dst,
|
||||
const FileMetaData& meta, size_t ts_sz) {
|
||||
const FileMetaData& meta,
|
||||
size_t ts_sz) const {
|
||||
if (ts_sz == 0 || meta.user_defined_timestamps_persisted) {
|
||||
PutLengthPrefixedSlice(dst, meta.smallest.Encode());
|
||||
PutLengthPrefixedSlice(dst, meta.largest.Encode());
|
||||
@@ -564,8 +545,7 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
break;
|
||||
|
||||
case kCompactCursor:
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetInternalKey(&input, &key)) {
|
||||
if (GetLevel(&input, &level, &msg) && GetInternalKey(&input, &key)) {
|
||||
// Here we re-use the output format of compact pointer in LevelDB
|
||||
// to persist compact_cursors_
|
||||
compact_cursors_.push_back(std::make_pair(level, key));
|
||||
@@ -578,8 +558,7 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
|
||||
case kDeletedFile: {
|
||||
uint64_t number = 0;
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetVarint64(&input, &number)) {
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number)) {
|
||||
deleted_files_.insert(std::make_pair(level, number));
|
||||
} else {
|
||||
if (!msg) {
|
||||
@@ -592,8 +571,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
case kNewFile: {
|
||||
uint64_t number = 0;
|
||||
uint64_t file_size = 0;
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetVarint64(&input, &number) && GetVarint64(&input, &file_size) &&
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest)) {
|
||||
f.fd = FileDescriptor(number, 0, file_size);
|
||||
@@ -610,8 +589,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
uint64_t file_size = 0;
|
||||
SequenceNumber smallest_seqno = 0;
|
||||
SequenceNumber largest_seqno = kMaxSequenceNumber;
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetVarint64(&input, &number) && GetVarint64(&input, &file_size) &&
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest) &&
|
||||
GetVarint64(&input, &smallest_seqno) &&
|
||||
@@ -633,9 +612,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
uint64_t file_size = 0;
|
||||
SequenceNumber smallest_seqno = 0;
|
||||
SequenceNumber largest_seqno = kMaxSequenceNumber;
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetVarint64(&input, &number) && GetVarint32(&input, &path_id) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint32(&input, &path_id) && GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest) &&
|
||||
GetVarint64(&input, &smallest_seqno) &&
|
||||
@@ -652,10 +630,7 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
}
|
||||
|
||||
case kNewFile4: {
|
||||
FileMetaData ignored_file;
|
||||
msg = DecodeNewFile4From(&input, max_level_, min_log_number_to_keep_,
|
||||
has_min_log_number_to_keep_, new_files_,
|
||||
ignored_file);
|
||||
msg = DecodeNewFile4From(&input);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -792,23 +767,6 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
}
|
||||
break;
|
||||
|
||||
case kSubcompactionProgress: {
|
||||
Slice encoded;
|
||||
if (!GetLengthPrefixedSlice(&input, &encoded)) {
|
||||
msg = "SubcompactionProgress not prefixed by length";
|
||||
break;
|
||||
}
|
||||
|
||||
SubcompactionProgress progress;
|
||||
Status s = progress.DecodeFrom(&encoded);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
SetSubcompactionProgress(progress);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if (tag & kTagSafeIgnoreMask) {
|
||||
// Tag from future which can be safely ignored.
|
||||
@@ -975,10 +933,6 @@ std::string VersionEdit::DebugString(bool hex_key) const {
|
||||
r.append("\n FullHistoryTsLow: ");
|
||||
r.append(Slice(full_history_ts_low_).ToString(hex_key));
|
||||
}
|
||||
if (HasSubcompactionProgress()) {
|
||||
r.append("\n SubcompactionProgress: ");
|
||||
r.append(subcompaction_progress_.ToString());
|
||||
}
|
||||
r.append("\n}\n");
|
||||
return r;
|
||||
}
|
||||
@@ -1128,301 +1082,9 @@ std::string VersionEdit::DebugJSON(int edit_num, bool hex_key) const {
|
||||
jw << "FullHistoryTsLow" << Slice(full_history_ts_low_).ToString(hex_key);
|
||||
}
|
||||
|
||||
if (HasSubcompactionProgress()) {
|
||||
jw << "SubcompactionProgress" << subcompaction_progress_.ToString();
|
||||
}
|
||||
|
||||
jw.EndObject();
|
||||
|
||||
return jw.Get();
|
||||
}
|
||||
|
||||
void SubcompactionProgressPerLevel::EncodeTo(std::string* dst) const {
|
||||
if (num_processed_output_records_ > 0) {
|
||||
PutVarint32(
|
||||
dst,
|
||||
SubcompactionProgressPerLevelCustomTag::kNumProcessedOutputRecords);
|
||||
std::string varint_records;
|
||||
PutVarint64(&varint_records, num_processed_output_records_);
|
||||
PutLengthPrefixedSlice(dst, varint_records);
|
||||
}
|
||||
|
||||
if (!output_files_.empty()) {
|
||||
PutVarint32(dst, SubcompactionProgressPerLevelCustomTag::kOutputFilesDelta);
|
||||
std::string files_data;
|
||||
EncodeOutputFiles(&files_data);
|
||||
PutLengthPrefixedSlice(dst, files_data);
|
||||
}
|
||||
|
||||
PutVarint32(dst, SubcompactionProgressPerLevelCustomTag::
|
||||
kSubcompactionProgressPerLevelTerminate);
|
||||
}
|
||||
|
||||
Status SubcompactionProgressPerLevel::DecodeFrom(Slice* input) {
|
||||
Clear();
|
||||
|
||||
while (true) {
|
||||
uint32_t tag = 0;
|
||||
if (!GetVarint32(input, &tag)) {
|
||||
return Status::Corruption("SubcompactionProgressPerLevel", "tag error");
|
||||
}
|
||||
|
||||
if (tag == SubcompactionProgressPerLevelCustomTag::
|
||||
kSubcompactionProgressPerLevelTerminate) {
|
||||
break;
|
||||
}
|
||||
|
||||
Slice field;
|
||||
if (!GetLengthPrefixedSlice(input, &field)) {
|
||||
return Status::Corruption("SubcompactionProgressPerLevel",
|
||||
"field length prefixed slice error");
|
||||
}
|
||||
|
||||
switch (tag) {
|
||||
case SubcompactionProgressPerLevelCustomTag::kNumProcessedOutputRecords: {
|
||||
if (!GetVarint64(&field, &num_processed_output_records_)) {
|
||||
return Status::Corruption("SubcompactionProgressPerLevel",
|
||||
"invalid num_processed_output_records_");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SubcompactionProgressPerLevelCustomTag::kOutputFilesDelta: {
|
||||
Status s = DecodeOutputFiles(&field, output_files_);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Forward compatibility: Handle unknown tags
|
||||
if ((tag & SubcompactionProgressPerLevelCustomTag::
|
||||
kSubcompactionProgressPerLevelCustomTagSafeIgnoreMask) !=
|
||||
0) {
|
||||
break;
|
||||
} else {
|
||||
return Status::NotSupported("SubcompactionProgress",
|
||||
"unsupported critical custom field");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void SubcompactionProgressPerLevel::EncodeOutputFiles(std::string* dst) const {
|
||||
size_t new_files_count =
|
||||
output_files_.size() > last_persisted_output_files_count_
|
||||
? output_files_.size() - last_persisted_output_files_count_
|
||||
: 0;
|
||||
|
||||
assert(new_files_count > 0);
|
||||
|
||||
PutVarint32(dst, static_cast<uint32_t>(new_files_count));
|
||||
|
||||
for (size_t i = last_persisted_output_files_count_; i < output_files_.size();
|
||||
++i) {
|
||||
std::string file_dst;
|
||||
bool ignored_min_log_written = false;
|
||||
|
||||
VersionEdit::EncodeToNewFile4(
|
||||
output_files_[i], -1 /* level */, 0 /* ts_sz */,
|
||||
false /* has_min_log_number_to_keep */, 0 /* min_log_number_to_keep */,
|
||||
ignored_min_log_written, &file_dst);
|
||||
|
||||
PutLengthPrefixedSlice(dst, file_dst);
|
||||
}
|
||||
}
|
||||
|
||||
Status SubcompactionProgressPerLevel::DecodeOutputFiles(
|
||||
Slice* input, autovector<FileMetaData>& output_files) {
|
||||
uint32_t new_file_count = 0;
|
||||
if (!GetVarint32(input, &new_file_count)) {
|
||||
return Status::Corruption("SubcompactionProgressPerLevel",
|
||||
"new output file count");
|
||||
}
|
||||
|
||||
assert(output_files.size() == 0);
|
||||
|
||||
output_files.reserve(new_file_count);
|
||||
|
||||
for (uint32_t i = 0; i < new_file_count; ++i) {
|
||||
Slice file_input;
|
||||
if (!GetLengthPrefixedSlice(input, &file_input)) {
|
||||
return Status::Corruption("SubcompactionProgressPerLevel",
|
||||
"output file metadata");
|
||||
}
|
||||
|
||||
uint32_t tag = 0;
|
||||
if (!GetVarint32(&file_input, &tag) || tag != kNewFile4) {
|
||||
return Status::Corruption("SubcompactionProgressPerLevel",
|
||||
"expected kNewFile4 tag");
|
||||
}
|
||||
|
||||
int ignored_max_level = -1;
|
||||
uint64_t ignored_min_log_number_to_keep = 0;
|
||||
bool ignored_has_min_log_number_to_keep = false;
|
||||
VersionEdit::NewFiles ignored_new_files;
|
||||
FileMetaData file;
|
||||
|
||||
const char* err = VersionEdit::DecodeNewFile4From(
|
||||
&file_input, ignored_max_level, ignored_min_log_number_to_keep,
|
||||
ignored_has_min_log_number_to_keep, ignored_new_files, file);
|
||||
|
||||
if (err != nullptr) {
|
||||
return Status::Corruption("SubcompactionProgressPerLevel", err);
|
||||
}
|
||||
|
||||
output_files.push_back(std::move(file));
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void SubcompactionProgress::EncodeTo(std::string* dst) const {
|
||||
if (!next_internal_key_to_compact.empty()) {
|
||||
PutVarint32(dst, SubcompactionProgressCustomTag::kNextInternalKeyToCompact);
|
||||
PutLengthPrefixedSlice(dst, next_internal_key_to_compact);
|
||||
}
|
||||
|
||||
PutVarint32(dst, SubcompactionProgressCustomTag::kNumProcessedInputRecords);
|
||||
std::string varint_records;
|
||||
PutVarint64(&varint_records, num_processed_input_records);
|
||||
PutLengthPrefixedSlice(dst, varint_records);
|
||||
|
||||
if (output_level_progress.GetOutputFiles().size() >
|
||||
output_level_progress.GetLastPersistedOutputFilesCount()) {
|
||||
PutVarint32(dst, SubcompactionProgressCustomTag::kOutputLevelProgress);
|
||||
std::string level_progress_data;
|
||||
output_level_progress.EncodeTo(&level_progress_data);
|
||||
PutLengthPrefixedSlice(dst, level_progress_data);
|
||||
}
|
||||
|
||||
if (proximal_output_level_progress.GetOutputFiles().size() >
|
||||
proximal_output_level_progress.GetLastPersistedOutputFilesCount()) {
|
||||
PutVarint32(dst,
|
||||
SubcompactionProgressCustomTag::kProximalOutputLevelProgress);
|
||||
std::string level_progress_data;
|
||||
proximal_output_level_progress.EncodeTo(&level_progress_data);
|
||||
PutLengthPrefixedSlice(dst, level_progress_data);
|
||||
}
|
||||
PutVarint32(dst,
|
||||
SubcompactionProgressCustomTag::kSubcompactionProgressTerminate);
|
||||
}
|
||||
|
||||
Status SubcompactionProgress::DecodeFrom(Slice* input) {
|
||||
Clear();
|
||||
|
||||
while (true) {
|
||||
uint32_t custom_tag = 0;
|
||||
if (!GetVarint32(input, &custom_tag)) {
|
||||
return Status::Corruption("SubcompactionProgress",
|
||||
"custom field tag error");
|
||||
}
|
||||
|
||||
if (custom_tag ==
|
||||
SubcompactionProgressCustomTag::kSubcompactionProgressTerminate) {
|
||||
break;
|
||||
}
|
||||
|
||||
Slice field;
|
||||
if (!GetLengthPrefixedSlice(input, &field)) {
|
||||
return Status::Corruption("SubcompactionProgress",
|
||||
"custom field length prefixed slice error");
|
||||
}
|
||||
|
||||
switch (custom_tag) {
|
||||
case SubcompactionProgressCustomTag::kNextInternalKeyToCompact:
|
||||
next_internal_key_to_compact = field.ToString();
|
||||
break;
|
||||
|
||||
case SubcompactionProgressCustomTag::kNumProcessedInputRecords:
|
||||
if (!GetVarint64(&field, &num_processed_input_records)) {
|
||||
return Status::Corruption("SubcompactionProgress",
|
||||
"invalid num_processed_input_records");
|
||||
}
|
||||
break;
|
||||
|
||||
case SubcompactionProgressCustomTag::kOutputLevelProgress: {
|
||||
Status s = output_level_progress.DecodeFrom(&field);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SubcompactionProgressCustomTag::kProximalOutputLevelProgress: {
|
||||
Status s = proximal_output_level_progress.DecodeFrom(&field);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
if ((custom_tag & SubcompactionProgressCustomTag::
|
||||
kSubcompactionProgressCustomTagSafeIgnoreMask) !=
|
||||
0) {
|
||||
break;
|
||||
} else {
|
||||
return Status::NotSupported("SubcompactionProgress",
|
||||
"unsupported critical custom field");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
bool SubcompactionProgressBuilder::ProcessVersionEdit(const VersionEdit& edit) {
|
||||
if (!edit.HasSubcompactionProgress()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const SubcompactionProgress& progress = edit.GetSubcompactionProgress();
|
||||
|
||||
MergeDeltaProgress(progress);
|
||||
|
||||
has_subcompaction_progress_ = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SubcompactionProgressBuilder::MergeDeltaProgress(
|
||||
const SubcompactionProgress& delta_progress) {
|
||||
accumulated_subcompaction_progress_.next_internal_key_to_compact =
|
||||
delta_progress.next_internal_key_to_compact;
|
||||
|
||||
accumulated_subcompaction_progress_.num_processed_input_records =
|
||||
delta_progress.num_processed_input_records;
|
||||
|
||||
MaybeMergeDeltaProgressPerLevel(
|
||||
accumulated_subcompaction_progress_.output_level_progress,
|
||||
delta_progress.output_level_progress);
|
||||
|
||||
MaybeMergeDeltaProgressPerLevel(
|
||||
accumulated_subcompaction_progress_.proximal_output_level_progress,
|
||||
delta_progress.proximal_output_level_progress);
|
||||
}
|
||||
|
||||
void SubcompactionProgressBuilder::MaybeMergeDeltaProgressPerLevel(
|
||||
SubcompactionProgressPerLevel& accumulated_level_progress,
|
||||
const SubcompactionProgressPerLevel& delta_level_progress) {
|
||||
const auto& delta_files = delta_level_progress.GetOutputFiles();
|
||||
if (delta_files.empty()) {
|
||||
return;
|
||||
}
|
||||
for (const FileMetaData& file : delta_files) {
|
||||
accumulated_level_progress.AddToOutputFiles(file); // Stored as copy
|
||||
}
|
||||
|
||||
accumulated_level_progress.SetNumProcessedOutputRecords(
|
||||
delta_level_progress.GetNumProcessedOutputRecords());
|
||||
}
|
||||
|
||||
void SubcompactionProgressBuilder::Clear() {
|
||||
accumulated_subcompaction_progress_.Clear();
|
||||
has_subcompaction_progress_ = false;
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+8
-231
@@ -72,23 +72,6 @@ enum Tag : uint32_t {
|
||||
kWalAddition2,
|
||||
kWalDeletion2,
|
||||
kPersistUserDefinedTimestamps,
|
||||
kSubcompactionProgress,
|
||||
};
|
||||
|
||||
enum SubcompactionProgressPerLevelCustomTag : uint32_t {
|
||||
kSubcompactionProgressPerLevelTerminate = 1, // End of fields marker
|
||||
kOutputFilesDelta = 2,
|
||||
kNumProcessedOutputRecords = 3,
|
||||
kSubcompactionProgressPerLevelCustomTagSafeIgnoreMask = 1 << 16,
|
||||
};
|
||||
|
||||
enum SubcompactionProgressCustomTag : uint32_t {
|
||||
kSubcompactionProgressTerminate = 1, // End of fields marker
|
||||
kNextInternalKeyToCompact = 2,
|
||||
kNumProcessedInputRecords = 3,
|
||||
kOutputLevelProgress = 4,
|
||||
kProximalOutputLevelProgress = 5,
|
||||
kSubcompactionProgressCustomTagSafeIgnoreMask = 1 << 16,
|
||||
};
|
||||
|
||||
enum NewFileCustomTag : uint32_t {
|
||||
@@ -457,194 +440,12 @@ struct LevelFilesBrief {
|
||||
}
|
||||
};
|
||||
|
||||
struct SubcompactionProgressPerLevel {
|
||||
uint64_t GetNumProcessedOutputRecords() const {
|
||||
return num_processed_output_records_;
|
||||
}
|
||||
|
||||
void SetNumProcessedOutputRecords(uint64_t num) {
|
||||
num_processed_output_records_ = num;
|
||||
}
|
||||
|
||||
const autovector<FileMetaData>& GetOutputFiles() const {
|
||||
return output_files_;
|
||||
}
|
||||
|
||||
void AddToOutputFiles(const FileMetaData& file) {
|
||||
output_files_.push_back(file);
|
||||
}
|
||||
|
||||
size_t GetLastPersistedOutputFilesCount() const {
|
||||
return last_persisted_output_files_count_;
|
||||
}
|
||||
|
||||
void UpdateLastPersistedOutputFilesCount() {
|
||||
last_persisted_output_files_count_ = output_files_.size();
|
||||
}
|
||||
|
||||
void EncodeTo(std::string* dst) const;
|
||||
|
||||
Status DecodeFrom(Slice* input);
|
||||
|
||||
void Clear() {
|
||||
num_processed_output_records_ = 0;
|
||||
output_files_.clear();
|
||||
last_persisted_output_files_count_ = 0;
|
||||
}
|
||||
|
||||
std::string ToString() const {
|
||||
std::ostringstream oss;
|
||||
oss << "SubcompactionProgressPerLevel{";
|
||||
oss << " num_processed_output_records=" << num_processed_output_records_;
|
||||
oss << ", output_files_count=" << output_files_.size();
|
||||
oss << ", last_persisted_output_files_count="
|
||||
<< last_persisted_output_files_count_;
|
||||
oss << " }";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
void TEST_ClearOutputFiles() { output_files_.clear(); }
|
||||
|
||||
private:
|
||||
uint64_t num_processed_output_records_ = 0;
|
||||
|
||||
autovector<FileMetaData> output_files_ = {};
|
||||
|
||||
// Number of files already persisted to help calculate the new output files to
|
||||
// persist in the future. This is to prevent having to persist all the output
|
||||
// files metadata so far every time of a "snapshot" of a progress is persisted
|
||||
// which can lead to O(1+2+...+n) = O(n^2) file metadata being persisted. The
|
||||
// current approach of persisting only the delta should always persist
|
||||
// exactly the number (n) of output files in total.
|
||||
size_t last_persisted_output_files_count_ = 0;
|
||||
|
||||
void EncodeOutputFiles(std::string* dst) const;
|
||||
|
||||
Status DecodeOutputFiles(Slice* input,
|
||||
autovector<FileMetaData>& temp_storage);
|
||||
};
|
||||
|
||||
struct SubcompactionProgress {
|
||||
std::string next_internal_key_to_compact;
|
||||
|
||||
uint64_t num_processed_input_records = 0;
|
||||
|
||||
SubcompactionProgressPerLevel output_level_progress;
|
||||
|
||||
SubcompactionProgressPerLevel proximal_output_level_progress;
|
||||
|
||||
SubcompactionProgress() = default;
|
||||
|
||||
void Clear() {
|
||||
next_internal_key_to_compact.clear();
|
||||
num_processed_input_records = 0;
|
||||
output_level_progress.Clear();
|
||||
proximal_output_level_progress.Clear();
|
||||
}
|
||||
|
||||
void EncodeTo(std::string* dst) const;
|
||||
|
||||
Status DecodeFrom(Slice* input);
|
||||
|
||||
std::string ToString() const {
|
||||
std::ostringstream oss;
|
||||
oss << "SubcompactionProgress{";
|
||||
oss << " next_internal_key_to_compact=";
|
||||
if (next_internal_key_to_compact.empty()) {
|
||||
oss << "";
|
||||
} else {
|
||||
ParsedInternalKey parsed_key;
|
||||
Slice key_slice(next_internal_key_to_compact);
|
||||
if (ParseInternalKey(key_slice, &parsed_key, false /* log_err_key */)
|
||||
.ok()) {
|
||||
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=";
|
||||
if (parsed_key.type == kValueTypeForSeek) {
|
||||
oss << "kValueTypeForSeek";
|
||||
} else {
|
||||
oss << static_cast<int>(parsed_key.type);
|
||||
}
|
||||
} else {
|
||||
oss << "raw=" << key_slice.ToString(true /* hex */);
|
||||
}
|
||||
}
|
||||
oss << ", num_processed_input_records=" << num_processed_input_records;
|
||||
oss << ", output_level_progress=" << output_level_progress.ToString();
|
||||
oss << ", proximal_output_level_progress="
|
||||
<< proximal_output_level_progress.ToString();
|
||||
oss << " }";
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
|
||||
class VersionEdit;
|
||||
|
||||
// Builder class to reconstruct complete subcompaction progress object
|
||||
// from multiple decoded VersionEdits containing delta output files information
|
||||
// of the same subcompaction. See
|
||||
// `SubcompactionProgressPerLevel::last_persisted_output_files_count_`'s comment
|
||||
//
|
||||
// WARNING: This class currently assumes all input VersionEdits contain progress
|
||||
// information for the SAME subcompaction. It does not validate
|
||||
// progress data from different subcompactions so mixing progress from
|
||||
// multiple subcompactions can result in corrupted state silently. The caller is
|
||||
// responsible for ensuring all VersionEdits processed by a single instance
|
||||
// of this builder correspond to the same subcompaction.
|
||||
class SubcompactionProgressBuilder {
|
||||
public:
|
||||
SubcompactionProgressBuilder() = default;
|
||||
|
||||
bool ProcessVersionEdit(const VersionEdit& edit);
|
||||
|
||||
const SubcompactionProgress& GetAccumulatedSubcompactionProgress() const {
|
||||
return accumulated_subcompaction_progress_;
|
||||
}
|
||||
|
||||
bool HasAccumulatedSubcompactionProgress() const {
|
||||
return has_subcompaction_progress_;
|
||||
}
|
||||
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
void MergeDeltaProgress(const SubcompactionProgress& delta_progress);
|
||||
|
||||
void MaybeMergeDeltaProgressPerLevel(
|
||||
SubcompactionProgressPerLevel& accumulated_level_progress,
|
||||
const SubcompactionProgressPerLevel& delta_level_progress);
|
||||
|
||||
SubcompactionProgress accumulated_subcompaction_progress_;
|
||||
bool has_subcompaction_progress_ = false;
|
||||
};
|
||||
|
||||
// Type alias for backward compatibility - vector of subcompaction progress
|
||||
using CompactionProgress = std::vector<SubcompactionProgress>;
|
||||
|
||||
// The state of a DB at any given time is referred to as a Version.
|
||||
// Any modification to the Version is considered a Version Edit. A Version is
|
||||
// constructed by joining a sequence of Version Edits. Version Edits are written
|
||||
// to the MANIFEST file.
|
||||
class VersionEdit {
|
||||
public:
|
||||
// Retrieve the table files added as well as their associated levels.
|
||||
using NewFiles = std::vector<std::pair<int, FileMetaData>>;
|
||||
|
||||
static void EncodeToNewFile4(const FileMetaData& f, int level, size_t ts_sz,
|
||||
bool has_min_log_number_to_keep,
|
||||
uint64_t min_log_number_to_keep,
|
||||
bool& min_log_num_written, std::string* dst);
|
||||
|
||||
static const char* DecodeNewFile4From(Slice* input, int& max_level,
|
||||
uint64_t& min_log_number_to_keep,
|
||||
bool& has_min_log_number_to_keep,
|
||||
NewFiles& new_files, FileMetaData& f);
|
||||
|
||||
void Clear();
|
||||
|
||||
void SetDBId(const std::string& db_id) {
|
||||
@@ -763,6 +564,8 @@ class VersionEdit {
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the table files added as well as their associated levels.
|
||||
using NewFiles = std::vector<std::pair<int, FileMetaData>>;
|
||||
const NewFiles& GetNewFiles() const { return new_files_; }
|
||||
|
||||
NewFiles& GetMutableNewFiles() { return new_files_; }
|
||||
@@ -932,22 +735,6 @@ class VersionEdit {
|
||||
full_history_ts_low_ = std::move(full_history_ts_low);
|
||||
}
|
||||
|
||||
void SetSubcompactionProgress(const SubcompactionProgress& progress) {
|
||||
has_subcompaction_progress_ = true;
|
||||
subcompaction_progress_ = progress;
|
||||
}
|
||||
|
||||
bool HasSubcompactionProgress() const { return has_subcompaction_progress_; }
|
||||
|
||||
const SubcompactionProgress& GetSubcompactionProgress() const {
|
||||
return subcompaction_progress_;
|
||||
}
|
||||
|
||||
void ClearSubcompactionProgress() {
|
||||
has_subcompaction_progress_ = false;
|
||||
subcompaction_progress_.Clear();
|
||||
}
|
||||
|
||||
// return true on success.
|
||||
// `ts_sz` is the size in bytes for the user-defined timestamp contained in
|
||||
// a user key. This argument is optional because it's only required for
|
||||
@@ -970,22 +757,15 @@ class VersionEdit {
|
||||
std::string DebugJSON(int edit_num, bool hex_key = false) const;
|
||||
|
||||
private:
|
||||
// Decode level information from serialized VersionEdit data and and track the
|
||||
// maximum level seen.
|
||||
//
|
||||
// Parameters:
|
||||
// input: Pointer to serialized data slice
|
||||
// level: Output parameter for the decoded level value
|
||||
// max_level: get updated if the decoded level is higher than passed in
|
||||
// value
|
||||
//
|
||||
// Returns: true on successful decode, false on parse error
|
||||
static bool GetLevel(Slice* input, int* level, int& max_level);
|
||||
bool GetLevel(Slice* input, int* level, const char** msg);
|
||||
|
||||
const char* DecodeNewFile4From(Slice* input);
|
||||
|
||||
// Encode file boundaries `FileMetaData.smallest` and `FileMetaData.largest`.
|
||||
// User-defined timestamps in the user key will be stripped if they shouldn't
|
||||
// be persisted.
|
||||
static void EncodeFileBoundaries(std::string* dst, const FileMetaData& meta,
|
||||
size_t ts_sz);
|
||||
void EncodeFileBoundaries(std::string* dst, const FileMetaData& meta,
|
||||
size_t ts_sz) const;
|
||||
|
||||
int max_level_ = 0;
|
||||
std::string db_id_;
|
||||
@@ -1036,9 +816,6 @@ class VersionEdit {
|
||||
std::string full_history_ts_low_;
|
||||
bool persist_user_defined_timestamps_ = true;
|
||||
|
||||
bool has_subcompaction_progress_ = false;
|
||||
SubcompactionProgress subcompaction_progress_;
|
||||
|
||||
// Newly created table files and blob files are eligible for deletion if they
|
||||
// are not registered as live files after the background jobs creating them
|
||||
// have finished. In case committing the VersionEdit containing such changes
|
||||
|
||||
@@ -794,339 +794,6 @@ TEST(FileMetaDataTest, UpdateBoundariesBlobIndex) {
|
||||
}
|
||||
}
|
||||
|
||||
class SubcompactionProgressTest : public VersionEditTest {
|
||||
protected:
|
||||
static constexpr uint64_t kTestFileSize = 1024;
|
||||
static constexpr SequenceNumber kTestSmallestSeq = 50;
|
||||
static constexpr SequenceNumber kTestLargestSeq = 150;
|
||||
static constexpr uint64_t kTestOldestAncesterTime = 12345;
|
||||
static constexpr uint64_t kTestFileCreationTime = 67890;
|
||||
static constexpr uint64_t kTestEpochNumber = 10;
|
||||
static const std::string kTestChecksumFuncName;
|
||||
|
||||
FileMetaData CreateTestFile(uint64_t file_number, const std::string& prefix) {
|
||||
FileMetaData file;
|
||||
file.fd = FileDescriptor(file_number, 0, kTestFileSize, kTestSmallestSeq,
|
||||
kTestLargestSeq);
|
||||
file.smallest = InternalKey(prefix + "a", kTestSmallestSeq, kTypeValue);
|
||||
file.largest = InternalKey(prefix + "z", kTestLargestSeq, kTypeValue);
|
||||
file.oldest_ancester_time = kTestOldestAncesterTime;
|
||||
file.file_creation_time = kTestFileCreationTime;
|
||||
file.epoch_number = kTestEpochNumber;
|
||||
file.file_checksum = "checksum_" + std::to_string(file_number);
|
||||
file.file_checksum_func_name = kTestChecksumFuncName;
|
||||
file.marked_for_compaction = false;
|
||||
file.temperature = Temperature::kUnknown;
|
||||
return file;
|
||||
}
|
||||
|
||||
// Store external file metadata objects for testing
|
||||
// These simulate files owned by CompactionOutputs
|
||||
std::vector<FileMetaData> compaction_output_files_;
|
||||
std::vector<FileMetaData> proximal_level_compaction_output_files_;
|
||||
|
||||
SubcompactionProgress CreateSubcompactionProgress(
|
||||
const std::string& next_key, uint64_t num_processed_input_records,
|
||||
uint64_t num_processed_output_records,
|
||||
uint64_t num_processed_proximal_level_output_records,
|
||||
const std::vector<uint64_t>& output_file_numbers = {},
|
||||
const std::vector<uint64_t>& proximal_file_numbers = {},
|
||||
const std::string& file_prefix = "file_") {
|
||||
SubcompactionProgress progress;
|
||||
progress.next_internal_key_to_compact = next_key;
|
||||
progress.num_processed_input_records = num_processed_input_records;
|
||||
progress.output_level_progress.SetNumProcessedOutputRecords(
|
||||
num_processed_output_records);
|
||||
progress.proximal_output_level_progress.SetNumProcessedOutputRecords(
|
||||
num_processed_proximal_level_output_records);
|
||||
|
||||
for (uint64_t file_num : output_file_numbers) {
|
||||
FileMetaData file = CreateTestFile(file_num, file_prefix + "output_");
|
||||
progress.output_level_progress.AddToOutputFiles(file);
|
||||
}
|
||||
|
||||
for (uint64_t file_num : proximal_file_numbers) {
|
||||
FileMetaData file = CreateTestFile(file_num, file_prefix + "proximal_");
|
||||
progress.proximal_output_level_progress.AddToOutputFiles(file);
|
||||
}
|
||||
|
||||
return progress;
|
||||
}
|
||||
|
||||
std::pair<const VersionEdit, const SubcompactionProgress>
|
||||
EncodeDecodeProgress(const SubcompactionProgress& progress) {
|
||||
VersionEdit edit;
|
||||
edit.SetSubcompactionProgress(progress);
|
||||
|
||||
std::string encoded;
|
||||
EXPECT_TRUE(edit.EncodeTo(&encoded, 0 /* ts_sz */));
|
||||
|
||||
VersionEdit decoded_edit;
|
||||
EXPECT_OK(decoded_edit.DecodeFrom(encoded));
|
||||
EXPECT_TRUE(decoded_edit.HasSubcompactionProgress());
|
||||
|
||||
SubcompactionProgress decoded_progress =
|
||||
decoded_edit.GetSubcompactionProgress();
|
||||
|
||||
return {std::move(decoded_edit), std::move(decoded_progress)};
|
||||
}
|
||||
|
||||
void VerifyFileMetaDataEquality(const FileMetaData& expected,
|
||||
const FileMetaData& actual) {
|
||||
// Verify the major fields only
|
||||
ASSERT_EQ(actual.fd.GetNumber(), expected.fd.GetNumber());
|
||||
ASSERT_EQ(actual.fd.GetFileSize(), expected.fd.GetFileSize());
|
||||
ASSERT_EQ(actual.smallest.Encode(), expected.smallest.Encode());
|
||||
ASSERT_EQ(actual.largest.Encode(), expected.largest.Encode());
|
||||
ASSERT_EQ(actual.oldest_ancester_time, expected.oldest_ancester_time);
|
||||
ASSERT_EQ(actual.file_creation_time, expected.file_creation_time);
|
||||
ASSERT_EQ(actual.epoch_number, expected.epoch_number);
|
||||
ASSERT_EQ(actual.file_checksum, expected.file_checksum);
|
||||
ASSERT_EQ(actual.file_checksum_func_name, expected.file_checksum_func_name);
|
||||
ASSERT_EQ(actual.marked_for_compaction, expected.marked_for_compaction);
|
||||
ASSERT_EQ(actual.temperature, expected.temperature);
|
||||
}
|
||||
|
||||
void VerifyProgressEquality(const SubcompactionProgress& expected,
|
||||
const SubcompactionProgress& actual) {
|
||||
ASSERT_EQ(actual.next_internal_key_to_compact,
|
||||
expected.next_internal_key_to_compact);
|
||||
|
||||
ASSERT_EQ(actual.num_processed_input_records,
|
||||
expected.num_processed_input_records);
|
||||
|
||||
for (const bool is_proximal_level : {false, true}) {
|
||||
const SubcompactionProgressPerLevel&
|
||||
actual_subcompaction_progress_by_level =
|
||||
is_proximal_level ? actual.proximal_output_level_progress
|
||||
: actual.output_level_progress;
|
||||
|
||||
const SubcompactionProgressPerLevel&
|
||||
expected_subcompaction_progress_by_level =
|
||||
is_proximal_level ? expected.proximal_output_level_progress
|
||||
: expected.output_level_progress;
|
||||
|
||||
ASSERT_EQ(
|
||||
actual_subcompaction_progress_by_level.GetNumProcessedOutputRecords(),
|
||||
expected_subcompaction_progress_by_level
|
||||
.GetNumProcessedOutputRecords());
|
||||
|
||||
ASSERT_EQ(
|
||||
actual_subcompaction_progress_by_level.GetOutputFiles().size(),
|
||||
expected_subcompaction_progress_by_level.GetOutputFiles().size());
|
||||
|
||||
for (size_t i = 0;
|
||||
i < expected_subcompaction_progress_by_level.GetOutputFiles().size();
|
||||
++i) {
|
||||
VerifyFileMetaDataEquality(
|
||||
expected_subcompaction_progress_by_level.GetOutputFiles()[i],
|
||||
actual_subcompaction_progress_by_level.GetOutputFiles()[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const std::string SubcompactionProgressTest::kTestChecksumFuncName = "crc32c";
|
||||
|
||||
TEST_F(SubcompactionProgressTest, BasicEncodeDecode) {
|
||||
// Create progress with files for both levels
|
||||
SubcompactionProgress progress = CreateSubcompactionProgress(
|
||||
"key_100", // next_internal_key_to_compact
|
||||
500, // num_processed_input_records
|
||||
400, // num_processed_output_records
|
||||
100, // num_processed_proximal_level_output_records
|
||||
{1}, // output_file_numbers
|
||||
{2}, // proximal_file_numbers
|
||||
"test_" // file_prefix
|
||||
);
|
||||
|
||||
auto [ignored, decoded_progress] = EncodeDecodeProgress(progress);
|
||||
|
||||
VerifyProgressEquality(progress, decoded_progress);
|
||||
}
|
||||
|
||||
TEST_F(SubcompactionProgressTest, OutputFilesDeltaEncodeDecode) {
|
||||
// Test Delta Encoding/Decoding
|
||||
SubcompactionProgress initial_progress = CreateSubcompactionProgress(
|
||||
"key_100", // next_internal_key_to_compact
|
||||
100, // num_processed_input_records
|
||||
40, // num_processed_output_records
|
||||
60, // num_processed_proximal_level_output_records
|
||||
{1}, // output_file_numbers
|
||||
{2}, // proximal_file_numbers
|
||||
"initial_" // file_prefix
|
||||
);
|
||||
|
||||
auto [initial_decoded_edit, ignored_1] =
|
||||
EncodeDecodeProgress(initial_progress);
|
||||
initial_progress.output_level_progress.UpdateLastPersistedOutputFilesCount();
|
||||
initial_progress.proximal_output_level_progress
|
||||
.UpdateLastPersistedOutputFilesCount();
|
||||
|
||||
// Add one new output file to output and proximal level
|
||||
SubcompactionProgress updated_progress = initial_progress;
|
||||
updated_progress.next_internal_key_to_compact = "key_300";
|
||||
updated_progress.num_processed_input_records = 1000;
|
||||
updated_progress.output_level_progress.SetNumProcessedOutputRecords(400);
|
||||
updated_progress.proximal_output_level_progress.SetNumProcessedOutputRecords(
|
||||
600);
|
||||
|
||||
FileMetaData new_file = CreateTestFile(3, "new_");
|
||||
updated_progress.output_level_progress.AddToOutputFiles(new_file);
|
||||
|
||||
FileMetaData new_file_proximal = CreateTestFile(4, "new_");
|
||||
updated_progress.proximal_output_level_progress.AddToOutputFiles(
|
||||
new_file_proximal);
|
||||
|
||||
auto [delta_decoded_edit, delta_decoded_progress] =
|
||||
EncodeDecodeProgress(updated_progress);
|
||||
|
||||
ASSERT_EQ(delta_decoded_progress.next_internal_key_to_compact,
|
||||
updated_progress.next_internal_key_to_compact);
|
||||
|
||||
ASSERT_EQ(delta_decoded_progress.num_processed_input_records,
|
||||
updated_progress.num_processed_input_records);
|
||||
|
||||
for (const bool& is_proximal_level : {false, true}) {
|
||||
const SubcompactionProgressPerLevel& delta_progress_per_level =
|
||||
is_proximal_level
|
||||
? delta_decoded_progress.proximal_output_level_progress
|
||||
: delta_decoded_progress.output_level_progress;
|
||||
|
||||
const SubcompactionProgressPerLevel& updated_progress_per_level =
|
||||
is_proximal_level ? updated_progress.proximal_output_level_progress
|
||||
: updated_progress.output_level_progress;
|
||||
|
||||
ASSERT_EQ(delta_progress_per_level.GetNumProcessedOutputRecords(),
|
||||
updated_progress_per_level.GetNumProcessedOutputRecords());
|
||||
|
||||
// Only the newly added file since last persistence should be present
|
||||
ASSERT_EQ(delta_progress_per_level.GetOutputFiles().size(), 1);
|
||||
|
||||
ASSERT_EQ(delta_progress_per_level.GetOutputFiles()[0].fd.GetNumber(),
|
||||
is_proximal_level ? new_file_proximal.fd.GetNumber()
|
||||
: new_file.fd.GetNumber());
|
||||
}
|
||||
|
||||
// Test SubcompactionProgressBuilder
|
||||
SubcompactionProgressBuilder builder;
|
||||
ASSERT_FALSE(builder.HasAccumulatedSubcompactionProgress());
|
||||
|
||||
ASSERT_TRUE(builder.ProcessVersionEdit(initial_decoded_edit));
|
||||
ASSERT_TRUE(builder.HasAccumulatedSubcompactionProgress());
|
||||
ASSERT_TRUE(builder.ProcessVersionEdit(delta_decoded_edit));
|
||||
|
||||
const auto& accumulated_progress =
|
||||
builder.GetAccumulatedSubcompactionProgress();
|
||||
|
||||
ASSERT_EQ(accumulated_progress.next_internal_key_to_compact,
|
||||
updated_progress.next_internal_key_to_compact);
|
||||
|
||||
ASSERT_EQ(accumulated_progress.num_processed_input_records,
|
||||
updated_progress.num_processed_input_records);
|
||||
|
||||
for (const bool& is_proximal_level : {false, true}) {
|
||||
const SubcompactionProgressPerLevel& accumulated_progress_per_level =
|
||||
is_proximal_level ? accumulated_progress.proximal_output_level_progress
|
||||
: accumulated_progress.output_level_progress;
|
||||
|
||||
const SubcompactionProgressPerLevel& updated_progress_per_level =
|
||||
is_proximal_level ? updated_progress.proximal_output_level_progress
|
||||
: updated_progress.output_level_progress;
|
||||
|
||||
ASSERT_EQ(accumulated_progress_per_level.GetNumProcessedOutputRecords(),
|
||||
updated_progress_per_level.GetNumProcessedOutputRecords());
|
||||
|
||||
ASSERT_EQ(accumulated_progress_per_level.GetOutputFiles().size(),
|
||||
updated_progress_per_level.GetOutputFiles().size());
|
||||
|
||||
std::set<uint64_t> accumulated_file_numbers;
|
||||
for (const auto& file : accumulated_progress_per_level.GetOutputFiles()) {
|
||||
accumulated_file_numbers.insert(file.fd.GetNumber());
|
||||
}
|
||||
|
||||
std::set<uint64_t> expected_file_numbers;
|
||||
for (const auto& file : updated_progress_per_level.GetOutputFiles()) {
|
||||
expected_file_numbers.insert(file.fd.GetNumber());
|
||||
}
|
||||
|
||||
ASSERT_EQ(accumulated_file_numbers, expected_file_numbers);
|
||||
}
|
||||
|
||||
// ===== PART 3: Test Builder Reset =====
|
||||
builder.Clear();
|
||||
ASSERT_FALSE(builder.HasAccumulatedSubcompactionProgress());
|
||||
}
|
||||
|
||||
TEST_F(SubcompactionProgressTest, UnknownTags) {
|
||||
SubcompactionProgress progress;
|
||||
std::string encoded;
|
||||
|
||||
// 1. Test unknown ignorable tag
|
||||
progress.next_internal_key_to_compact = "test_key";
|
||||
progress.num_processed_input_records = 100;
|
||||
|
||||
PutVarint32(&encoded,
|
||||
SubcompactionProgressCustomTag::kNextInternalKeyToCompact);
|
||||
PutLengthPrefixedSlice(&encoded, progress.next_internal_key_to_compact);
|
||||
|
||||
PutVarint32(&encoded,
|
||||
SubcompactionProgressCustomTag::kNumProcessedInputRecords);
|
||||
std::string varint_records;
|
||||
PutVarint64(&varint_records, progress.num_processed_input_records);
|
||||
PutLengthPrefixedSlice(&encoded, varint_records);
|
||||
|
||||
// Manually encode with unknown ignorable tag (has
|
||||
// SubcompactionProgressCustomTag::kSubcompactionProgressCustomTagSafeIgnoreMask
|
||||
// bit set)
|
||||
uint32_t unknown_ignorable_tag =
|
||||
SubcompactionProgressCustomTag::
|
||||
kSubcompactionProgressCustomTagSafeIgnoreMask +
|
||||
1;
|
||||
PutVarint32(&encoded, unknown_ignorable_tag);
|
||||
PutLengthPrefixedSlice(&encoded, "future_data");
|
||||
|
||||
PutVarint32(&encoded,
|
||||
SubcompactionProgressCustomTag::kSubcompactionProgressTerminate);
|
||||
|
||||
// Test decoding - should succeed and ignore unknown tag
|
||||
Slice input(encoded);
|
||||
SubcompactionProgress decoded_progress;
|
||||
Status s = decoded_progress.DecodeFrom(&input);
|
||||
ASSERT_OK(s);
|
||||
|
||||
// Verify known fields are preserved
|
||||
ASSERT_EQ(decoded_progress.next_internal_key_to_compact,
|
||||
progress.next_internal_key_to_compact);
|
||||
ASSERT_EQ(decoded_progress.num_processed_input_records,
|
||||
progress.num_processed_input_records);
|
||||
|
||||
// 2. Test unknown non-ignorable tag
|
||||
encoded.clear();
|
||||
PutVarint32(&encoded,
|
||||
SubcompactionProgressCustomTag::kNextInternalKeyToCompact);
|
||||
PutLengthPrefixedSlice(&encoded, "test_key");
|
||||
|
||||
// Manually encode with unknown non-ignorable tag (do not have
|
||||
// SubcompactionProgressCustomTag::kSubcompactionProgressCustomTagSafeIgnoreMask
|
||||
// bit set)
|
||||
uint32_t unknown_critical_tag =
|
||||
SubcompactionProgressCustomTag::
|
||||
kSubcompactionProgressCustomTagSafeIgnoreMask -
|
||||
1;
|
||||
PutVarint32(&encoded, unknown_critical_tag);
|
||||
PutLengthPrefixedSlice(&encoded, "critical_future_data");
|
||||
PutVarint32(&encoded,
|
||||
SubcompactionProgressCustomTag::kSubcompactionProgressTerminate);
|
||||
|
||||
// Test decoding - should fail on critical unknown tag
|
||||
Slice critical_input(encoded);
|
||||
SubcompactionProgress critical_progress;
|
||||
Status critical_status = critical_progress.DecodeFrom(&critical_input);
|
||||
ASSERT_NOK(critical_status);
|
||||
ASSERT_TRUE(critical_status.IsNotSupported());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+93
-380
@@ -16,7 +16,6 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -981,8 +980,7 @@ class LevelIterator final : public InternalIterator {
|
||||
nullptr,
|
||||
bool allow_unprepared_value = false,
|
||||
std::unique_ptr<TruncatedRangeDelIterator>*** range_tombstone_iter_ptr_ =
|
||||
nullptr,
|
||||
Statistics* db_statistics = nullptr, SystemClock* clock = nullptr)
|
||||
nullptr)
|
||||
: table_cache_(table_cache),
|
||||
read_options_(read_options),
|
||||
file_options_(file_options),
|
||||
@@ -1007,9 +1005,7 @@ class LevelIterator final : public InternalIterator {
|
||||
allow_unprepared_value_(allow_unprepared_value),
|
||||
is_next_read_sequential_(false),
|
||||
to_return_sentinel_(false),
|
||||
scan_opts_(nullptr),
|
||||
db_statistics_(db_statistics),
|
||||
clock_(clock) {
|
||||
scan_opts_(nullptr) {
|
||||
// Empty level is not supported.
|
||||
assert(flevel_ != nullptr && flevel_->num_files > 0);
|
||||
if (range_tombstone_iter_ptr_) {
|
||||
@@ -1017,15 +1013,7 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
~LevelIterator() override {
|
||||
delete file_iter_.Set(nullptr);
|
||||
// Clean up any prepared iterators that weren't used
|
||||
for (auto& entry : prepared_iters_) {
|
||||
delete entry.second;
|
||||
}
|
||||
prepared_iters_.clear();
|
||||
assert(prepared_iters_.size() == 0);
|
||||
}
|
||||
~LevelIterator() override { delete file_iter_.Set(nullptr); }
|
||||
|
||||
// Seek to the first file with a key >= target.
|
||||
// If range_tombstone_iter_ is not nullptr, then we pretend that file
|
||||
@@ -1113,40 +1101,14 @@ class LevelIterator final : public InternalIterator {
|
||||
read_seq_ = read_seq;
|
||||
}
|
||||
|
||||
inline bool FileHasMultiScanArg(size_t file_index) {
|
||||
if (file_to_scan_opts_.get()) {
|
||||
auto it = file_to_scan_opts_->find(file_index);
|
||||
if (it != file_to_scan_opts_->end()) {
|
||||
return !it->second.empty();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MultiScanArgs& GetMultiScanArgForFile(size_t file_index) {
|
||||
auto multi_scan_args_it = file_to_scan_opts_->find(file_index);
|
||||
if (multi_scan_args_it == file_to_scan_opts_->end()) {
|
||||
auto ret = file_to_scan_opts_->emplace(
|
||||
file_index, MultiScanArgs(user_comparator_.user_comparator()));
|
||||
multi_scan_args_it = ret.first;
|
||||
assert(ret.second);
|
||||
}
|
||||
return multi_scan_args_it->second;
|
||||
}
|
||||
|
||||
void Prepare(const MultiScanArgs* so) override {
|
||||
// We assume here that scan_opts is sorted such that
|
||||
// scan_opts[0].range.start < scan_opts[1].range.start, and non
|
||||
// overlapping
|
||||
// scan_opts[0].range.start < scan_opts[1].range.start, and non overlapping
|
||||
if (so == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
scan_opts_ = so;
|
||||
|
||||
// Verify comparator is consistent
|
||||
assert(so->GetComparator() == user_comparator_.user_comparator());
|
||||
|
||||
file_to_scan_opts_ = std::make_unique<ScanOptionsMap>();
|
||||
for (size_t k = 0; k < scan_opts_->size(); k++) {
|
||||
const ScanOptions& opt = scan_opts_->GetScanRanges().at(k);
|
||||
@@ -1162,26 +1124,8 @@ class LevelIterator final : public InternalIterator {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t timestamp_size =
|
||||
user_comparator_.user_comparator()->timestamp_size();
|
||||
InternalKey istart, iend;
|
||||
if (timestamp_size == 0) {
|
||||
istart =
|
||||
InternalKey(start.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
// end key is exclusive for multiscan
|
||||
iend = InternalKey(end.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
} else {
|
||||
std::string start_key_with_ts, end_key_with_ts;
|
||||
AppendKeyWithMaxTimestamp(&start_key_with_ts, start.value(),
|
||||
timestamp_size);
|
||||
AppendKeyWithMaxTimestamp(&end_key_with_ts, end.value(),
|
||||
timestamp_size);
|
||||
istart = InternalKey(start_key_with_ts, kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
// end key is exclusive for multiscan
|
||||
iend =
|
||||
InternalKey(end_key_with_ts, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
}
|
||||
InternalKey istart(start.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey iend(end.value(), 0, kValueTypeForSeekForPrev);
|
||||
|
||||
// TODO: This needs to be optimized, right now we iterate twice, which
|
||||
// we dont need to. We can do this in N rather than 2N.
|
||||
@@ -1195,51 +1139,14 @@ class LevelIterator final : public InternalIterator {
|
||||
// 3. [ S ] ...... [ E ]
|
||||
for (auto i = fstart; i <= fend; i++) {
|
||||
if (i < flevel_->num_files) {
|
||||
// FindFile only compares against the largest_key, so we need this
|
||||
// additional check to ensure the scan range overlaps the file
|
||||
if (icomparator_.InternalKeyComparator::Compare(
|
||||
iend.Encode(), flevel_->files[i].smallest_key) < 0) {
|
||||
continue;
|
||||
}
|
||||
auto const metadata = flevel_->files[i].file_metadata;
|
||||
if (metadata->FileIsStandAloneRangeTombstone()) {
|
||||
// Skip stand alone range deletion files.
|
||||
continue;
|
||||
}
|
||||
auto& args = GetMultiScanArgForFile(i);
|
||||
args.insert(start.value(), end.value(), opt.property_bag);
|
||||
(*file_to_scan_opts_)[i].insert(start.value(), end.value(),
|
||||
opt.property_bag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StopWatch timer(clock_, db_statistics_, MULTISCAN_PREPARE_ITERATORS);
|
||||
|
||||
// Propagate multiscan configs
|
||||
// Propagate io colaescing threshold
|
||||
for (auto& file_to_arg : *file_to_scan_opts_) {
|
||||
file_to_arg.second.CopyConfigFrom(*so);
|
||||
assert(OverlapRange(*file_to_arg.second.GetScanRanges().begin(),
|
||||
file_to_arg.first) &&
|
||||
OverlapRange(*file_to_arg.second.GetScanRanges().rbegin(),
|
||||
file_to_arg.first));
|
||||
}
|
||||
|
||||
if (so->use_async_io) {
|
||||
auto before = file_index_;
|
||||
// Pre-create and prepare only relevant file iterators
|
||||
for (auto& file_to_arg : *file_to_scan_opts_) {
|
||||
size_t file_index = file_to_arg.first;
|
||||
|
||||
file_index_ = file_index;
|
||||
// Create iterator for this file
|
||||
auto iter = NewFileIterator();
|
||||
if (iter != nullptr) {
|
||||
// If we have async enabled, lets prepare all our iterators.
|
||||
iter->Prepare(&file_to_arg.second);
|
||||
// Store the prepared iterator
|
||||
prepared_iters_[file_index] = iter;
|
||||
}
|
||||
}
|
||||
file_index_ = before;
|
||||
file_to_arg.second.io_coalesce_threshold = so->io_coalesce_threshold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,10 +1222,6 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool OverlapRange(const ScanOptions& opts, size_t file_index);
|
||||
#endif
|
||||
|
||||
TableCache* table_cache_;
|
||||
const ReadOptions& read_options_;
|
||||
const FileOptions& file_options_;
|
||||
@@ -1374,15 +1277,9 @@ class LevelIterator final : public InternalIterator {
|
||||
bool to_return_sentinel_ = false;
|
||||
const MultiScanArgs* scan_opts_ = nullptr;
|
||||
|
||||
Statistics* db_statistics_ = nullptr;
|
||||
SystemClock* clock_ = nullptr;
|
||||
|
||||
// Our stored scan_opts for each prefix
|
||||
std::unique_ptr<ScanOptionsMap> file_to_scan_opts_ = nullptr;
|
||||
|
||||
// Map to store pre-created iterators by file index
|
||||
std::unordered_map<size_t, InternalIterator*> prepared_iters_;
|
||||
|
||||
// Sets flags for if we should return the sentinel key next.
|
||||
// The condition for returning sentinel is reaching the end of current
|
||||
// file_iter_: !Valid() && status.().ok().
|
||||
@@ -1422,14 +1319,6 @@ void LevelIterator::Seek(const Slice& target) {
|
||||
}
|
||||
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
if (scan_opts_) {
|
||||
// At this point, we only know that the seek target is < largest_key
|
||||
// in the file. We need to check whether there is actual overlap.
|
||||
const FdWithKeyRange& cur_file = flevel_->files[file_index_];
|
||||
if (KeyReachedUpperBound(cur_file.smallest_key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
file_iter_.Seek(target);
|
||||
// Status::TryAgain indicates asynchronous request for retrieval of data
|
||||
// blocks has been submitted. So it should return at this point and Seek
|
||||
@@ -1654,24 +1543,12 @@ bool LevelIterator::SkipEmptyFileForward() {
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
// If we are doing prepared scan opts then we should seek to the values
|
||||
// specified by the scan opts
|
||||
|
||||
if (scan_opts_ && FileHasMultiScanArg(file_index_)) {
|
||||
if (scan_opts_ && (*file_to_scan_opts_)[file_index_].size()) {
|
||||
const ScanOptions& opts =
|
||||
GetMultiScanArgForFile(file_index_).GetScanRanges().front();
|
||||
file_to_scan_opts_->at(file_index_).GetScanRanges().front();
|
||||
if (opts.range.start.has_value()) {
|
||||
InternalKey target;
|
||||
const size_t ts_size =
|
||||
user_comparator_.user_comparator()->timestamp_size();
|
||||
if (ts_size == 0) {
|
||||
target = InternalKey(opts.range.start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
} else {
|
||||
std::string seek_key;
|
||||
AppendKeyWithMaxTimestamp(&seek_key, opts.range.start.value(),
|
||||
ts_size);
|
||||
target =
|
||||
InternalKey(seek_key, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
}
|
||||
InternalKey target(*opts.range.start.AsPtr(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
file_iter_.Seek(target.Encode());
|
||||
}
|
||||
} else {
|
||||
@@ -1718,25 +1595,22 @@ void LevelIterator::SkipEmptyFileBackward() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool LevelIterator::OverlapRange(const ScanOptions& opts, size_t file_index) {
|
||||
return (user_comparator_.CompareWithoutTimestamp(
|
||||
opts.range.start.value(), /*a_has_ts=*/false,
|
||||
ExtractUserKey(flevel_->files[file_index].largest_key),
|
||||
/*b_has_ts=*/true) <= 0 &&
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
opts.range.limit.value(), /*a_has_ts=*/false,
|
||||
ExtractUserKey(flevel_->files[file_index].smallest_key),
|
||||
/*b_has_ts=*/true) > 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void LevelIterator::SetFileIterator(InternalIterator* iter) {
|
||||
if (pinned_iters_mgr_ && iter) {
|
||||
iter->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
InternalIterator* old_iter = file_iter_.Set(iter);
|
||||
if (iter && scan_opts_) {
|
||||
if (file_to_scan_opts_.get() &&
|
||||
file_to_scan_opts_->find(file_index_) != file_to_scan_opts_->end()) {
|
||||
const MultiScanArgs& new_opts = file_to_scan_opts_->at(file_index_);
|
||||
file_iter_.Prepare(&new_opts);
|
||||
} else {
|
||||
file_iter_.Prepare(scan_opts_);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the read pattern for PrefetchBuffer.
|
||||
if (is_next_read_sequential_) {
|
||||
file_iter_.UpdateReadaheadState(old_iter);
|
||||
@@ -1766,24 +1640,7 @@ void LevelIterator::InitFileIterator(size_t new_file_index) {
|
||||
// no need to change anything
|
||||
} else {
|
||||
file_index_ = new_file_index;
|
||||
if (!prepared_iters_.empty()) {
|
||||
auto prepared_it = prepared_iters_.find(file_index_);
|
||||
if (prepared_it != prepared_iters_.end()) {
|
||||
InternalIterator* iter = prepared_it->second;
|
||||
prepared_iters_.erase(prepared_it);
|
||||
SetFileIterator(iter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
InternalIterator* iter = NewFileIterator();
|
||||
if (FileHasMultiScanArg(file_index_)) {
|
||||
auto& args = GetMultiScanArgForFile(file_index_);
|
||||
assert(OverlapRange(*args.GetScanRanges().begin(), file_index_) &&
|
||||
OverlapRange(*args.GetScanRanges().rbegin(), file_index_));
|
||||
iter->Prepare(&args);
|
||||
}
|
||||
|
||||
SetFileIterator(iter);
|
||||
}
|
||||
}
|
||||
@@ -2089,79 +1946,6 @@ void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) {
|
||||
}
|
||||
}
|
||||
|
||||
void Version::GetColumnFamilyMetaData(
|
||||
const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* cf_meta) {
|
||||
assert(cf_meta);
|
||||
assert(cfd_);
|
||||
|
||||
cf_meta->name = cfd_->GetName();
|
||||
cf_meta->size = 0;
|
||||
cf_meta->file_count = 0;
|
||||
cf_meta->levels.clear();
|
||||
cf_meta->blob_file_size = 0;
|
||||
cf_meta->blob_file_count = 0;
|
||||
cf_meta->blob_files.clear();
|
||||
|
||||
const auto& ioptions = cfd_->ioptions();
|
||||
auto* vstorage = storage_info();
|
||||
|
||||
int first_level = (options.level >= 0) ? options.level : 0;
|
||||
int last_level =
|
||||
(options.level >= 0) ? options.level + 1 : cfd_->NumberLevels();
|
||||
|
||||
InternalKey ikey_start, ikey_end;
|
||||
const InternalKey* begin = nullptr;
|
||||
const InternalKey* end = nullptr;
|
||||
|
||||
if (options.range.start.has_value()) {
|
||||
ikey_start = InternalKey(options.range.start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
begin = &ikey_start;
|
||||
}
|
||||
|
||||
if (options.range.limit.has_value()) {
|
||||
ikey_end = InternalKey(options.range.limit.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
end = &ikey_end;
|
||||
}
|
||||
|
||||
for (int l = first_level; l < last_level; ++l) {
|
||||
uint64_t level_size = 0;
|
||||
std::vector<SstFileMetaData> files;
|
||||
std::vector<FileMetaData*> overlapping_files;
|
||||
vstorage->GetOverlappingInputs(l, begin, end, &overlapping_files);
|
||||
|
||||
for (const auto& file : overlapping_files) {
|
||||
uint32_t path_id = file->fd.GetPathId();
|
||||
const auto& file_path = (path_id < ioptions.cf_paths.size())
|
||||
? ioptions.cf_paths[path_id].path
|
||||
: ioptions.cf_paths.back().path;
|
||||
const uint64_t file_number = file->fd.GetNumber();
|
||||
files.emplace_back(
|
||||
MakeTableFileName("", file_number), file_number, file_path,
|
||||
file->fd.GetFileSize(), file->fd.smallest_seqno,
|
||||
file->fd.largest_seqno, file->smallest.user_key().ToString(),
|
||||
file->largest.user_key().ToString(),
|
||||
file->stats.num_reads_sampled.load(std::memory_order_relaxed),
|
||||
file->being_compacted, file->temperature,
|
||||
file->oldest_blob_file_number, file->TryGetOldestAncesterTime(),
|
||||
file->TryGetFileCreationTime(), file->epoch_number,
|
||||
file->file_checksum, file->file_checksum_func_name);
|
||||
files.back().num_entries = file->num_entries;
|
||||
files.back().num_deletions = file->num_deletions;
|
||||
files.back().smallest = file->smallest.Encode().ToString();
|
||||
files.back().largest = file->largest.Encode().ToString();
|
||||
level_size += file->fd.GetFileSize();
|
||||
cf_meta->file_count++;
|
||||
}
|
||||
if (!files.empty()) {
|
||||
cf_meta->levels.emplace_back(l, level_size, std::move(files));
|
||||
cf_meta->size += level_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Version::GetSstFilesSize() {
|
||||
uint64_t sst_files_size = 0;
|
||||
for (int level = 0; level < storage_info_.num_levels_; level++) {
|
||||
@@ -2246,7 +2030,7 @@ InternalIterator* Version::TEST_GetLevelIterator(
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
nullptr /* range_del_agg */, nullptr /* compaction_boundaries */,
|
||||
allow_unprepared_value, &tombstone_iter_ptr, db_statistics_, clock_);
|
||||
allow_unprepared_value, &tombstone_iter_ptr);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(level_iter);
|
||||
} else {
|
||||
@@ -2386,7 +2170,7 @@ void Version::AddIteratorsForLevel(const ReadOptions& read_options,
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
/*range_del_agg=*/nullptr,
|
||||
/*compaction_boundaries=*/nullptr, allow_unprepared_value,
|
||||
&tombstone_iter_ptr, db_statistics_, clock_);
|
||||
&tombstone_iter_ptr);
|
||||
if (read_options.ignore_range_deletions) {
|
||||
merge_iter_builder->AddIterator(level_iter);
|
||||
} else {
|
||||
@@ -2443,7 +2227,7 @@ Status Version::OverlapWithLevelIterator(const ReadOptions& read_options,
|
||||
mutable_cf_options_, should_sample_file_read(),
|
||||
cfd_->internal_stats()->GetFileReadHist(level),
|
||||
TableReaderCaller::kUserIterator, IsFilterSkipped(level), level,
|
||||
&range_del_agg, nullptr, false, nullptr, db_statistics_, clock_));
|
||||
&range_del_agg, nullptr, false));
|
||||
status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key,
|
||||
iter.get(), overlap);
|
||||
}
|
||||
@@ -4625,8 +4409,7 @@ bool VersionStorageInfo::OverlapInLevel(int level,
|
||||
void VersionStorageInfo::GetOverlappingInputs(
|
||||
int level, const InternalKey* begin, const InternalKey* end,
|
||||
std::vector<FileMetaData*>* inputs, int hint_index, int* file_index,
|
||||
bool expand_range, const FileMetaData* starting_l0_file,
|
||||
InternalKey** next_smallest) const {
|
||||
bool expand_range, InternalKey** next_smallest) const {
|
||||
if (level >= num_non_empty_levels_) {
|
||||
// this level is empty, no overlapping inputs
|
||||
return;
|
||||
@@ -4659,19 +4442,7 @@ void VersionStorageInfo::GetOverlappingInputs(
|
||||
|
||||
// index stores the file index need to check.
|
||||
std::list<size_t> index;
|
||||
size_t start_index = 0;
|
||||
if (starting_l0_file != nullptr) {
|
||||
uint64_t starting_file_number = starting_l0_file->fd.GetNumber();
|
||||
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
|
||||
if (level_files_brief_[level].files[i].fd.GetNumber() ==
|
||||
starting_file_number) {
|
||||
start_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(start_index < level_files_brief_[level].num_files);
|
||||
}
|
||||
for (size_t i = start_index; i < level_files_brief_[level].num_files; i++) {
|
||||
for (size_t i = 0; i < level_files_brief_[level].num_files; i++) {
|
||||
index.emplace_back(i);
|
||||
}
|
||||
|
||||
@@ -5458,7 +5229,6 @@ void AtomicGroupReadBuffer::Clear() {
|
||||
|
||||
VersionSet::VersionSet(
|
||||
const std::string& dbname, const ImmutableDBOptions* _db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& storage_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
@@ -5487,7 +5257,6 @@ VersionSet::VersionSet(
|
||||
prev_log_number_(0),
|
||||
current_version_number_(0),
|
||||
manifest_file_size_(0),
|
||||
last_compacted_manifest_file_size_(0),
|
||||
file_options_(storage_options),
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
io_tracer_(io_tracer),
|
||||
@@ -5495,9 +5264,7 @@ VersionSet::VersionSet(
|
||||
offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)),
|
||||
error_handler_(error_handler),
|
||||
unchanging_(unchanging),
|
||||
closed_(false) {
|
||||
UpdatedMutableDbOptions(mutable_db_options, /*mu=*/nullptr);
|
||||
}
|
||||
closed_(false) {}
|
||||
|
||||
Status VersionSet::Close(FSDirectory* db_dir, InstrumentedMutex* mu) {
|
||||
Status s;
|
||||
@@ -5571,15 +5338,6 @@ void VersionSet::Reset() {
|
||||
if (column_family_set_) {
|
||||
WriteBufferManager* wbm = column_family_set_->write_buffer_manager();
|
||||
WriteController* wc = column_family_set_->write_controller();
|
||||
|
||||
// Clear TableCache to prevent use-after-free: Reset() deletes old
|
||||
// ColumnFamilySet but reuses table_cache_, which may contain
|
||||
// BlockBasedTable entries with dangling references to deleted CFD's
|
||||
// ioptions.
|
||||
if (table_cache_) {
|
||||
table_cache_->EraseUnRefEntries();
|
||||
}
|
||||
|
||||
// db_id becomes the source of truth after DBImpl::Recover():
|
||||
// https://github.com/facebook/rocksdb/blob/v7.3.1/db/db_impl/db_impl_open.cc#L527
|
||||
// Note: we may not be able to recover db_id from MANIFEST if
|
||||
@@ -5602,38 +5360,11 @@ void VersionSet::Reset() {
|
||||
current_version_number_ = 0;
|
||||
manifest_writers_.clear();
|
||||
manifest_file_size_ = 0;
|
||||
last_compacted_manifest_file_size_ = 0;
|
||||
TuneMaxManifestFileSize();
|
||||
obsolete_files_.clear();
|
||||
obsolete_manifests_.clear();
|
||||
wals_.Reset();
|
||||
}
|
||||
|
||||
void VersionSet::UpdatedMutableDbOptions(
|
||||
const MutableDBOptions& updated_options, InstrumentedMutex* mu) {
|
||||
// Must be holding mutex if not called during initialization
|
||||
if (mu) {
|
||||
mu->AssertHeld();
|
||||
} else {
|
||||
// manifest_file_size_ must be 0 if called from the constructor
|
||||
assert(manifest_file_size_ == 0);
|
||||
}
|
||||
file_options_.writable_file_max_buffer_size =
|
||||
updated_options.writable_file_max_buffer_size;
|
||||
min_max_manifest_file_size_ = updated_options.max_manifest_file_size;
|
||||
max_manifest_space_amp_pct_ = static_cast<unsigned>(
|
||||
std::max(updated_options.max_manifest_space_amp_pct, 0));
|
||||
manifest_preallocation_size_ = updated_options.manifest_preallocation_size;
|
||||
TuneMaxManifestFileSize();
|
||||
}
|
||||
|
||||
void VersionSet::TuneMaxManifestFileSize() {
|
||||
tuned_max_manifest_file_size_ =
|
||||
std::max(min_max_manifest_file_size_,
|
||||
last_compacted_manifest_file_size_ *
|
||||
(100U + max_manifest_space_amp_pct_) / 100U);
|
||||
}
|
||||
|
||||
void VersionSet::AppendVersion(ColumnFamilyData* column_family_data,
|
||||
Version* v) {
|
||||
// compute new compaction score
|
||||
@@ -5716,8 +5447,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// the preceding version edits in the same atomic group, and update
|
||||
// their `remaining_entries_` member variable because we are NOT going
|
||||
// to write the version edits' of dropped CF to the MANIFEST. If we
|
||||
// don't update, then Recover can report corrupted atomic group
|
||||
// because the `remaining_entries_` do not match.
|
||||
// don't update, then Recover can report corrupted atomic group because
|
||||
// the `remaining_entries_` do not match.
|
||||
if (!batch_edits.empty()) {
|
||||
if (batch_edits.back()->IsInAtomicGroup() &&
|
||||
batch_edits.back()->GetRemainingEntries() > 0) {
|
||||
@@ -5877,11 +5608,10 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
uint64_t prev_manifest_file_size = manifest_file_size_;
|
||||
assert(pending_manifest_file_number_ == 0);
|
||||
if (!skip_manifest_write &&
|
||||
(!descriptor_log_ ||
|
||||
prev_manifest_file_size >= tuned_max_manifest_file_size_)) {
|
||||
manifest_file_size_ > db_options_->max_manifest_file_size)) {
|
||||
TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:BeforeNewManifest");
|
||||
new_descriptor_log = true;
|
||||
} else {
|
||||
@@ -5921,8 +5651,6 @@ Status VersionSet::ProcessManifestWrites(
|
||||
IOStatus manifest_io_status;
|
||||
manifest_io_status.PermitUncheckedError();
|
||||
std::unique_ptr<log::Writer> new_desc_log_ptr;
|
||||
// Save before releasing mu
|
||||
uint64_t manifest_preallocation_size = manifest_preallocation_size_;
|
||||
if (skip_manifest_write) {
|
||||
if (s.ok()) {
|
||||
constexpr bool update_stats = true;
|
||||
@@ -5966,13 +5694,16 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// This is fine because everything inside of this block is serialized --
|
||||
// only one thread can be here at the same time
|
||||
// create new manifest file
|
||||
ROCKS_LOG_INFO(db_options_->info_log, "Creating manifest %" PRIu64 "\n",
|
||||
pending_manifest_file_number_);
|
||||
std::string descriptor_fname =
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_);
|
||||
std::unique_ptr<FSWritableFile> descriptor_file;
|
||||
io_s = NewWritableFile(fs_.get(), descriptor_fname, &descriptor_file,
|
||||
opt_file_opts);
|
||||
if (io_s.ok()) {
|
||||
descriptor_file->SetPreallocationBlockSize(manifest_preallocation_size);
|
||||
descriptor_file->SetPreallocationBlockSize(
|
||||
db_options_->manifest_preallocation_size);
|
||||
FileTypeSet tmp_set = db_options_->checksum_handoff_file_types;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(descriptor_file), descriptor_fname, opt_file_opts, clock_,
|
||||
@@ -6022,12 +5753,10 @@ Status VersionSet::ProcessManifestWrites(
|
||||
#ifndef NDEBUG
|
||||
if (batch_edits.size() > 1 && batch_edits.size() - 1 == idx) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
|
||||
"0",
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:0",
|
||||
nullptr);
|
||||
TEST_SYNC_POINT(
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:"
|
||||
"1");
|
||||
"VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:1");
|
||||
}
|
||||
++idx;
|
||||
#endif /* !NDEBUG */
|
||||
@@ -6064,8 +5793,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
file_options_.temperature, dir_contains_current_file);
|
||||
if (!io_s.ok()) {
|
||||
s = io_s;
|
||||
// Quarantine old manifest file in case new manifest file's CURRENT
|
||||
// file wasn't created successfully and the old manifest is needed.
|
||||
// Quarantine old manifest file in case new manifest file's CURRENT file
|
||||
// wasn't created successfully and the old manifest is needed.
|
||||
limbo_descriptor_log_file_number.push_back(manifest_file_number_);
|
||||
files_to_quarantine_if_commit_fail.push_back(
|
||||
&limbo_descriptor_log_file_number);
|
||||
@@ -6075,13 +5804,6 @@ Status VersionSet::ProcessManifestWrites(
|
||||
if (s.ok()) {
|
||||
// find offset in manifest file where this version is stored.
|
||||
new_manifest_file_size = raw_desc_log_ptr->file()->GetFileSize();
|
||||
if (new_descriptor_log) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log,
|
||||
"Created manifest %" PRIu64
|
||||
", compacted+appended from %" PRIu64 " to %" PRIu64 "\n",
|
||||
pending_manifest_file_number_, prev_manifest_file_size,
|
||||
new_manifest_file_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
|
||||
@@ -6130,8 +5852,6 @@ Status VersionSet::ProcessManifestWrites(
|
||||
descriptor_log_ = std::move(new_desc_log_ptr);
|
||||
obsolete_manifests_.emplace_back(
|
||||
DescriptorFileName("", manifest_file_number_));
|
||||
last_compacted_manifest_file_size_ = new_manifest_file_size;
|
||||
TuneMaxManifestFileSize();
|
||||
}
|
||||
|
||||
// Install the new versions
|
||||
@@ -6214,21 +5934,21 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// that renaming tmp file to CURRENT failed.
|
||||
//
|
||||
// On local POSIX-compliant FS, the CURRENT must point to the original
|
||||
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can
|
||||
// also keep it. Future recovery will ignore this MANIFEST. It's also ok
|
||||
// for the process not to crash and continue using the db. Any future
|
||||
// LogAndApply() call will switch to a new MANIFEST and update CURRENT,
|
||||
// still ignoring this one.
|
||||
// MANIFEST. We can delete the new MANIFEST for simplicity, but we can also
|
||||
// keep it. Future recovery will ignore this MANIFEST. It's also ok for the
|
||||
// process not to crash and continue using the db. Any future LogAndApply()
|
||||
// call will switch to a new MANIFEST and update CURRENT, still ignoring
|
||||
// this one.
|
||||
//
|
||||
// On non-local FS, it is
|
||||
// possible that the rename operation succeeded on the server (remote)
|
||||
// side, but the client somehow returns a non-ok status to RocksDB. Note
|
||||
// that this does not violate atomicity. Should we delete the new MANIFEST
|
||||
// successfully, a subsequent recovery attempt will likely see the CURRENT
|
||||
// pointing to the new MANIFEST, thus fail. We will not be able to open
|
||||
// the DB again. Therefore, if manifest operations succeed, we should keep
|
||||
// the the new MANIFEST. If the process proceeds, any future LogAndApply()
|
||||
// call will switch to a new MANIFEST and update CURRENT. If user tries to
|
||||
// pointing to the new MANIFEST, thus fail. We will not be able to open the
|
||||
// DB again. Therefore, if manifest operations succeed, we should keep the
|
||||
// the new MANIFEST. If the process proceeds, any future LogAndApply() call
|
||||
// will switch to a new MANIFEST and update CURRENT. If user tries to
|
||||
// re-open the DB,
|
||||
// a) CURRENT points to the new MANIFEST, and the new MANIFEST is present.
|
||||
// b) CURRENT points to the original MANIFEST, and the original MANIFEST
|
||||
@@ -6357,9 +6077,9 @@ Status VersionSet::LogAndApply(
|
||||
first_writer.cv.Wait();
|
||||
}
|
||||
if (first_writer.done) {
|
||||
// All non-CF-manipulation operations can be grouped together and
|
||||
// committed to MANIFEST. They should all have finished. The status code
|
||||
// is stored in the first manifest writer.
|
||||
// All non-CF-manipulation operations can be grouped together and committed
|
||||
// to MANIFEST. They should all have finished. The status code is stored in
|
||||
// the first manifest writer.
|
||||
#ifndef NDEBUG
|
||||
for (const auto& writer : writers) {
|
||||
assert(writer.done);
|
||||
@@ -6413,8 +6133,8 @@ void VersionSet::LogAndApplyCFHelper(VersionEdit* edit,
|
||||
assert(!edit->HasLastSequence());
|
||||
edit->SetLastSequence(*max_last_sequence);
|
||||
if (edit->IsColumnFamilyDrop()) {
|
||||
// if we drop column family, we have to make sure to save max column
|
||||
// family, so that we don't reuse existing ID
|
||||
// if we drop column family, we have to make sure to save max column family,
|
||||
// so that we don't reuse existing ID
|
||||
edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily());
|
||||
}
|
||||
}
|
||||
@@ -6703,8 +6423,7 @@ void VersionSet::RecoverEpochNumbers() {
|
||||
Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families,
|
||||
const std::string& dbname,
|
||||
FileSystem* fs) {
|
||||
// Read "CURRENT" file, which contains a pointer to the current manifest
|
||||
// file
|
||||
// Read "CURRENT" file, which contains a pointer to the current manifest file
|
||||
std::string manifest_path;
|
||||
uint64_t manifest_file_number;
|
||||
Status s = GetCurrentManifestPath(dbname, fs, /*is_retry=*/false,
|
||||
@@ -6766,16 +6485,14 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
ImmutableDBOptions imm_db_options(*options);
|
||||
MutableDBOptions mutable_db_options(*options);
|
||||
ImmutableDBOptions db_options(*options);
|
||||
ColumnFamilyOptions cf_options(*options);
|
||||
std::shared_ptr<Cache> tc(NewLRUCache(options->max_open_files - 10,
|
||||
options->table_cache_numshardbits));
|
||||
WriteController wc(options->delayed_write_rate);
|
||||
WriteBufferManager wb(options->db_write_buffer_size);
|
||||
VersionSet versions(dbname, &imm_db_options, mutable_db_options, file_options,
|
||||
tc.get(), &wb, &wc, nullptr /*BlockCacheTracer*/,
|
||||
nullptr /*IOTracer*/,
|
||||
VersionSet versions(dbname, &db_options, file_options, tc.get(), &wb, &wc,
|
||||
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/,
|
||||
/*db_id*/ "",
|
||||
/*db_session_id*/ "", options->daily_offpeak_time_utc,
|
||||
/*error_handler_*/ nullptr, /*unchanging=*/false);
|
||||
@@ -6861,9 +6578,9 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
|
||||
// Get the checksum information including the checksum and checksum function
|
||||
// name of all SST and blob files in VersionSet. Store the information in
|
||||
// FileChecksumList which contains a map from file number to its checksum
|
||||
// info. If DB is not running, make sure call VersionSet::Recover() to load
|
||||
// the file metadata from Manifest to VersionSet before calling this function.
|
||||
// FileChecksumList which contains a map from file number to its checksum info.
|
||||
// If DB is not running, make sure call VersionSet::Recover() to load the file
|
||||
// metadata from Manifest to VersionSet before calling this function.
|
||||
Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) {
|
||||
// Clean the previously stored checksum information if any.
|
||||
Status s;
|
||||
@@ -7005,8 +6722,8 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
// WARNING: This method doesn't hold a mutex!!
|
||||
|
||||
// This is done without DB mutex lock held, but only within single-threaded
|
||||
// LogAndApply. Column family manipulations can only happen within
|
||||
// LogAndApply (the same single thread), so we're safe to iterate.
|
||||
// LogAndApply. Column family manipulations can only happen within LogAndApply
|
||||
// (the same single thread), so we're safe to iterate.
|
||||
|
||||
assert(io_s.ok());
|
||||
if (db_options_->write_dbid_to_manifest) {
|
||||
@@ -7040,9 +6757,9 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
}
|
||||
|
||||
// New manifest should rollover the WAL deletion record from previous
|
||||
// manifest. Otherwise, when an addition record of a deleted WAL gets added
|
||||
// to this new manifest later (which can happens in e.g, SyncWAL()), this
|
||||
// new manifest creates an illusion that such WAL hasn't been deleted.
|
||||
// manifest. Otherwise, when an addition record of a deleted WAL gets added to
|
||||
// this new manifest later (which can happens in e.g, SyncWAL()), this new
|
||||
// manifest creates an illusion that such WAL hasn't been deleted.
|
||||
VersionEdit wal_deletions;
|
||||
wal_deletions.DeleteWalsBefore(min_log_number_to_keep());
|
||||
std::string wal_deletions_record;
|
||||
@@ -7174,9 +6891,9 @@ Status VersionSet::WriteCurrentStateToManifest(
|
||||
// TODO(aekmekji): in CompactionJob::GenSubcompactionBoundaries(), this
|
||||
// function is called repeatedly with consecutive pairs of slices. For example
|
||||
// if the slice list is [a, b, c, d] this function is called with arguments
|
||||
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible
|
||||
// where we avoid doing binary search for the keys b and c twice and instead
|
||||
// somehow maintain state of where they first appear in the files.
|
||||
// (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible where
|
||||
// we avoid doing binary search for the keys b and c twice and instead somehow
|
||||
// maintain state of where they first appear in the files.
|
||||
uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
|
||||
const ReadOptions& read_options,
|
||||
Version* v, const Slice& start,
|
||||
@@ -7197,20 +6914,19 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
|
||||
}
|
||||
|
||||
// Outline of the optimization that uses options.files_size_error_margin.
|
||||
// When approximating the files total size that is used to store a keys
|
||||
// range, we first sum up the sizes of the files that fully fall into the
|
||||
// range. Then we sum up the sizes of all the files that may intersect with
|
||||
// the range (this includes all files in L0 as well). Then, if
|
||||
// total_intersecting_size is smaller than total_full_size *
|
||||
// options.files_size_error_margin - we can infer that the intersecting
|
||||
// files have a sufficiently negligible contribution to the total size, and
|
||||
// we can approximate the storage required for the keys in range as just
|
||||
// half of the intersecting_files_size. E.g., if the value of
|
||||
// files_size_error_margin is 0.1, then the error of the approximation is
|
||||
// limited to only ~10% of the total size of files that fully fall into the
|
||||
// keys range. In such case, this helps to avoid a costly process of binary
|
||||
// searching the intersecting files that is required only for a more precise
|
||||
// calculation of the total size.
|
||||
// When approximating the files total size that is used to store a keys range,
|
||||
// we first sum up the sizes of the files that fully fall into the range.
|
||||
// Then we sum up the sizes of all the files that may intersect with the range
|
||||
// (this includes all files in L0 as well). Then, if total_intersecting_size
|
||||
// is smaller than total_full_size * options.files_size_error_margin - we can
|
||||
// infer that the intersecting files have a sufficiently negligible
|
||||
// contribution to the total size, and we can approximate the storage required
|
||||
// for the keys in range as just half of the intersecting_files_size.
|
||||
// E.g., if the value of files_size_error_margin is 0.1, then the error of the
|
||||
// approximation is limited to only ~10% of the total size of files that fully
|
||||
// fall into the keys range. In such case, this helps to avoid a costly
|
||||
// process of binary searching the intersecting files that is required only
|
||||
// for a more precise calculation of the total size.
|
||||
|
||||
autovector<FdWithKeyRange*, 32> first_files;
|
||||
autovector<FdWithKeyRange*, 16> last_files;
|
||||
@@ -7282,11 +6998,10 @@ uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options,
|
||||
total_intersecting_size += file_ptr->fd.GetFileSize();
|
||||
}
|
||||
|
||||
// Now scan all the first & last files at each level, and estimate their
|
||||
// size. If the total_intersecting_size is less than X% of the
|
||||
// total_full_size - we want to approximate the result in order to avoid the
|
||||
// costly binary search inside ApproximateSize. We use half of file size as
|
||||
// an approximation below.
|
||||
// Now scan all the first & last files at each level, and estimate their size.
|
||||
// If the total_intersecting_size is less than X% of the total_full_size - we
|
||||
// want to approximate the result in order to avoid the costly binary search
|
||||
// inside ApproximateSize. We use half of file size as an approximation below.
|
||||
|
||||
const double margin = options.files_size_error_margin;
|
||||
if (margin > 0 && total_intersecting_size <
|
||||
@@ -7554,8 +7269,7 @@ InternalIterator* VersionSet::MakeInputIterator(
|
||||
/*no per level latency histogram=*/nullptr,
|
||||
TableReaderCaller::kCompaction, /*skip_filters=*/false,
|
||||
/*level=*/static_cast<int>(c->level(which)), range_del_agg,
|
||||
c->boundaries(which), false, &tombstone_iter_ptr,
|
||||
db_options_->statistics.get(), clock_);
|
||||
c->boundaries(which), false, &tombstone_iter_ptr);
|
||||
range_tombstones.emplace_back(nullptr, tombstone_iter_ptr);
|
||||
}
|
||||
}
|
||||
@@ -7830,13 +7544,12 @@ Status VersionSet::VerifyFileMetadata(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
ReactiveVersionSet::ReactiveVersionSet(
|
||||
const std::string& dbname, const ImmutableDBOptions* imm_db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::string& dbname, const ImmutableDBOptions* _db_options,
|
||||
const FileOptions& _file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: VersionSet(dbname, imm_db_options, mutable_db_options, _file_options,
|
||||
table_cache, write_buffer_manager, write_controller,
|
||||
: VersionSet(dbname, _db_options, _file_options, table_cache,
|
||||
write_buffer_manager, write_controller,
|
||||
/*block_cache_tracer=*/nullptr, io_tracer, /*db_id*/ "",
|
||||
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false) {}
|
||||
@@ -7960,8 +7673,8 @@ Status ReactiveVersionSet::MaybeSwitchManifest(
|
||||
}
|
||||
} else if (s.IsPathNotFound()) {
|
||||
// This can happen if the primary switches to a new MANIFEST after the
|
||||
// secondary reads the CURRENT file but before the secondary actually
|
||||
// tries to open the MANIFEST.
|
||||
// secondary reads the CURRENT file but before the secondary actually tries
|
||||
// to open the MANIFEST.
|
||||
s = Status::TryAgain(
|
||||
"The primary may have switched to a new MANIFEST and deleted the old "
|
||||
"one.");
|
||||
|
||||
Loaded 100 of 302 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user