mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-07 11:32:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bf472dace | ||
|
|
3a196076d1 | ||
|
|
0a7ce8204d | ||
|
|
41c6640085 | ||
|
|
f324397aca | ||
|
|
4de3ce3b01 | ||
|
|
92ede49ffd | ||
|
|
840fad9610 | ||
|
|
2db33019f5 | ||
|
|
5202d73c1e | ||
|
|
e32c14eb56 | ||
|
|
6d9b526551 | ||
|
|
f343f7ecdc | ||
|
|
a8a5ade6fa | ||
|
|
3687dc4ad3 | ||
|
|
8edb99f904 | ||
|
|
622186adec | ||
|
|
ad83352c39 | ||
|
|
a1dad12c8c | ||
|
|
42842edc8d | ||
|
|
1d18c4ed01 | ||
|
|
f7e4009de1 | ||
|
|
112ff5bb70 | ||
|
|
1585f2240c | ||
|
|
04c085a3fa | ||
|
|
2d331cc125 | ||
|
|
f722e68d88 | ||
|
|
cbfcac8d1d | ||
|
|
5ace84ebae | ||
|
|
194160d534 | ||
|
|
4ab1bc865c | ||
|
|
27625f4fc2 | ||
|
|
bdf5a8fffb | ||
|
|
2fab774697 | ||
|
|
7c22fbe0d5 | ||
|
|
9d3afcf543 | ||
|
|
742741b175 | ||
|
|
1e5fa69c99 | ||
|
|
13172e2be3 | ||
|
|
035242415f | ||
|
|
f5fb597bac | ||
|
|
d8c058c5fe | ||
|
|
feb1486e37 | ||
|
|
c0e484c36e | ||
|
|
3d53af9746 | ||
|
|
e859c3b7af | ||
|
|
862438a7a1 | ||
|
|
1c8a012727 | ||
|
|
90241e18c8 | ||
|
|
169f90cdea | ||
|
|
134cfb6b22 | ||
|
|
6051d843d5 | ||
|
|
bbd8f0d4bf | ||
|
|
afbbc90b06 | ||
|
|
eaeafa7819 | ||
|
|
54373ba0e8 | ||
|
|
ab10ea0aac | ||
|
|
eb1d924308 | ||
|
|
7ae602e80a | ||
|
|
3cdd3281ba | ||
|
|
841e364238 | ||
|
|
73432a3f36 | ||
|
|
f9f408f536 | ||
|
|
a843991930 | ||
|
|
fa3e61cce2 | ||
|
|
19c8d1b7ed | ||
|
|
798373975c | ||
|
|
e9fc03eed7 | ||
|
|
ef6fbe7ff9 | ||
|
|
94e65a2e0b | ||
|
|
5a1ff2cb14 | ||
|
|
6a202c5570 | ||
|
|
6127a42f98 | ||
|
|
20bcd01758 | ||
|
|
631fb8670b | ||
|
|
3c85aa8a69 | ||
|
|
95813a84cd | ||
|
|
7c3472b4d9 | ||
|
|
2620c85638 | ||
|
|
29d9798ae8 | ||
|
|
acf9d4e445 | ||
|
|
54941a8d42 | ||
|
|
4f12c55e3e | ||
|
|
799f83a934 | ||
|
|
d87e598f70 | ||
|
|
f46242cef6 | ||
|
|
67af5bdc38 | ||
|
|
8b8a3de2c6 | ||
|
|
0e59c3864f | ||
|
|
85f1ba572e | ||
|
|
1aca60c089 | ||
|
|
86bb0c0d1b | ||
|
|
6b02f137a4 | ||
|
|
5a498bf688 | ||
|
|
96f796f93a | ||
|
|
0044a76d36 | ||
|
|
a805c9b9a8 | ||
|
|
dfbcdaf70e | ||
|
|
a34683bf54 | ||
|
|
8fa2aae7f4 | ||
|
|
fc8bc60f2d | ||
|
|
ac4d563dd1 | ||
|
|
2950e99219 | ||
|
|
68efd6fd8e | ||
|
|
e59bbd7241 | ||
|
|
749e11f0ad | ||
|
|
b67149a55e | ||
|
|
d399165109 | ||
|
|
8d2f420db2 | ||
|
|
1842a4029f |
No files matched your search
@@ -1,7 +1,17 @@
|
||||
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
|
||||
@@ -0,0 +1,26 @@
|
||||
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_COMMIT_HASH from Makefile
|
||||
id: extract-folly-hash
|
||||
shell: bash
|
||||
run: |
|
||||
FOLLY_COMMIT_HASH=$(grep '^FOLLY_COMMIT_HASH' Makefile | awk '{print $3}')
|
||||
echo "hash=$FOLLY_COMMIT_HASH" >> $GITHUB_OUTPUT
|
||||
- name: Cache folly build
|
||||
id: cache-folly-build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
# Cache the folly build directory
|
||||
path: /tmp/fbcode_builder_getdeps-Z__wZrocksdbZrocksdbZthird-partyZfollyZbuildZfbcode_builder-root/installed
|
||||
# Key is based on:
|
||||
# - OS and architecture
|
||||
# - The specific folly commit hash from Makefile
|
||||
# - The container image version to account for different compiler/library versions
|
||||
key: folly-build-${{ runner.os }}-${{ runner.arch }}-${{ steps.extract-folly-hash.outputs.hash }}-ubuntu22.1-v1
|
||||
@@ -3,5 +3,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout folly sources
|
||||
run: make checkout_folly
|
||||
run: |
|
||||
make checkout_folly
|
||||
echo "GETDEPS_USE_WGET=1" >> "$GITHUB_ENV"
|
||||
shell: bash
|
||||
@@ -4,6 +4,16 @@ 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
|
||||
@@ -38,11 +48,12 @@ runs:
|
||||
$env:Path = $env:JAVA_HOME + ";" + $env:Path
|
||||
mkdir build
|
||||
cd build
|
||||
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
|
||||
& cmake -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DWIN_CI=1 -DPORTABLE="$Env:CMAKE_PORTABLE" -DSNAPPY=1 -DXPRESS=1 -DJNI=1 ..
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
cd ..
|
||||
echo "Building with VS version: $Env:CMAKE_GENERATOR"
|
||||
msbuild build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
# 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
|
||||
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
|
||||
@@ -52,3 +63,7 @@ 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: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
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-13-asan-ubsan-with-folly:
|
||||
build-linux-clang-18-asan-ubsan-with-folly:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: clang-13
|
||||
CXX: clang++-13
|
||||
CC: clang-18
|
||||
CXX: clang++-18
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -107,41 +107,26 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- name: Build rocksdb lib
|
||||
run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j4 static_lib
|
||||
run: CC=clang-18 CXX=clang++-18 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-gcc-11-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: 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
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
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)"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 -DCMAKE_CXX_FLAGS=-DGLOG_USE_GLOG_EXPORT .. && make V=1 -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -97,16 +97,17 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
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_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:
|
||||
@@ -114,16 +115,17 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
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:
|
||||
@@ -131,32 +133,30 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
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 V=1 make -j32 all
|
||||
- run: USE_FOLLY_LITE=1 EXTRA_CXXFLAGS=-DGLOG_USE_GLOG_EXPORT 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: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
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 V=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-cmake-with-benchmark:
|
||||
@@ -164,7 +164,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -190,7 +190,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -211,68 +211,61 @@ jobs:
|
||||
- run: ls librocksdb.a
|
||||
- run: if ./trace_analyzer --version; then false; else true; fi
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang-no_test_run:
|
||||
build-linux-clang-13-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 8-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- run: CC=clang CXX=clang++ USE_CLANG=1 PORTABLE=1 make V=1 -j16 all
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang-13-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
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: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j32 all microbench
|
||||
# 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
|
||||
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 DEBUG_LEVEL=0 make -j32 release
|
||||
# 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
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-gcc-8-no_test_run:
|
||||
build-linux-clang-18-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: CC=gcc-8 CXX=g++-8 V=1 make -j32 all
|
||||
- run: CC=clang-18 CXX=clang++-18 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
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-gcc-10-cxx20-no_test_run:
|
||||
build-linux-gcc-14-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
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
|
||||
- run: CC=gcc-14 CXX=g++-14 V=1 make -j32 all microbench
|
||||
- uses: "./.github/actions/post-steps"
|
||||
|
||||
# ======================== Linux Other Checks ======================= #
|
||||
build-linux-clang10-clang-analyze:
|
||||
build-linux-clang18-clang-analyze:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- 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
|
||||
- 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
|
||||
- uses: "./.github/actions/post-steps"
|
||||
- name: compress test report
|
||||
run: tar -cvzf scan_build_report.tar.gz scan_build_report
|
||||
@@ -300,7 +293,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -308,90 +301,96 @@ 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-clang10-asan-ubsan:
|
||||
build-linux-clang18-asan-ubsan:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 32-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
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-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j40 check
|
||||
- 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
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang13-mini-tsan:
|
||||
build-linux-clang18-mini-tsan:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 32-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: COMPILE_WITH_TSAN=1 CC=clang-13 CXX=clang++-13 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 check
|
||||
- run: COMPILE_WITH_TSAN=1 CC=clang-18 CXX=clang++-18 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: zjay437/rocksdb:0.6
|
||||
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: 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
|
||||
- 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
|
||||
- uses: "./.github/actions/post-steps"
|
||||
# ========================= MacOS build only ======================== #
|
||||
build-macos:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15-xlarge
|
||||
env:
|
||||
ROCKSDB_DISABLE_JEMALLOC: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: 14.3.1
|
||||
xcode-version: 16.4.0
|
||||
- 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 -j16 all
|
||||
run: ulimit -S -n `ulimit -H -n` && make V=1 J=16 -j8 all
|
||||
- uses: "./.github/actions/post-steps"
|
||||
# ========================= MacOS with Tests ======================== #
|
||||
build-macos-cmake:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15-xlarge
|
||||
strategy:
|
||||
matrix:
|
||||
run_even_tests: [true, false]
|
||||
run_sharded_tests: [0, 1, 2, 3]
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: 14.3.1
|
||||
xcode-version: 16.4.0
|
||||
- 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 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 }}
|
||||
run: cd build && make V=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 }}
|
||||
- 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-2022
|
||||
runs-on: windows-8-core
|
||||
env:
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: 1
|
||||
@@ -404,11 +403,13 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:centos7_x64-be
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
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 }}
|
||||
@@ -425,18 +426,21 @@ jobs:
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- name: Test RocksDBJava
|
||||
run: scl enable devtoolset-7 'make V=1 J=8 -j8 jtest'
|
||||
# NOTE: post-steps skipped because of compatibility issues with docker image
|
||||
# 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
|
||||
build-linux-java-static:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:centos7_x64-be
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
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 }}
|
||||
@@ -453,11 +457,12 @@ jobs:
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- name: Build RocksDBJava Static Library
|
||||
run: scl enable devtoolset-7 'make V=1 J=8 -j8 rocksdbjavastatic'
|
||||
# NOTE: post-steps skipped because of compatibility issues with docker image
|
||||
# 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
|
||||
build-macos-java:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15-xlarge
|
||||
env:
|
||||
JAVA_HOME: "/Library/Java/JavaVirtualMachines/liberica-jdk-8.jdk/Contents/Home"
|
||||
ROCKSDB_DISABLE_JEMALLOC: 1
|
||||
@@ -465,7 +470,7 @@ jobs:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: maxim-lobanov/setup-xcode@v1.6.0
|
||||
with:
|
||||
xcode-version: 14.3.1
|
||||
xcode-version: 16.4.0
|
||||
- uses: "./.github/actions/increase-max-open-files-on-macos"
|
||||
- uses: "./.github/actions/install-gflags-on-macos"
|
||||
- uses: "./.github/actions/install-jdk8-on-macos"
|
||||
@@ -480,14 +485,14 @@ jobs:
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-macos-java-static:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15-xlarge
|
||||
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: 14.3.1
|
||||
xcode-version: 16.4.0
|
||||
- uses: "./.github/actions/increase-max-open-files-on-macos"
|
||||
- uses: "./.github/actions/install-gflags-on-macos"
|
||||
- uses: "./.github/actions/install-jdk8-on-macos"
|
||||
@@ -502,14 +507,14 @@ jobs:
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-macos-java-static-universal:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15-xlarge
|
||||
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: 14.3.1
|
||||
xcode-version: 16.4.0
|
||||
- uses: "./.github/actions/increase-max-open-files-on-macos"
|
||||
- uses: "./.github/actions/install-gflags-on-macos"
|
||||
- uses: "./.github/actions/install-jdk8-on-macos"
|
||||
|
||||
@@ -419,17 +419,19 @@ 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",
|
||||
@@ -451,6 +453,8 @@ 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)
|
||||
@@ -4840,6 +4844,12 @@ cpp_unittest_wrapper(name="db_encryption_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_etc3_test",
|
||||
srcs=["db/db_etc3_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_flush_test",
|
||||
srcs=["db/db_flush_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
@@ -5190,6 +5200,12 @@ cpp_unittest_wrapper(name="import_column_family_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="index_builder_test",
|
||||
srcs=["table/block_based/index_builder_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="inlineskiplist_test",
|
||||
srcs=["memtable/inlineskiplist_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
@@ -5382,6 +5398,12 @@ 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"],
|
||||
|
||||
+39
-17
@@ -27,7 +27,7 @@
|
||||
#
|
||||
# Linux:
|
||||
#
|
||||
# 1. Install a recent toolchain if you're on a older distro. C++17 required (GCC >= 7, Clang >= 5)
|
||||
# 1. Install a recent toolchain if you're on a older distro. C++20 required (GCC >= 11, Clang >= 10)
|
||||
# 2. mkdir build; cd build
|
||||
# 3. cmake ..
|
||||
# 4. make -j
|
||||
@@ -100,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 17)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
endif()
|
||||
|
||||
include(CMakeDependentOption)
|
||||
@@ -203,9 +203,16 @@ 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)
|
||||
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")
|
||||
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()
|
||||
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")
|
||||
@@ -313,8 +320,7 @@ if(NOT MSVC)
|
||||
endif()
|
||||
|
||||
# Check if -latomic is required or not
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++17")
|
||||
if (NOT MSVC AND NOT APPLE)
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
std::atomic<uint64_t> x(0);
|
||||
@@ -451,24 +457,33 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Used to run CI build and tests so we can run faster
|
||||
# Used to run optimized debug 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(OPTDBG)
|
||||
if (WIN_CI)
|
||||
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()
|
||||
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-")
|
||||
if(OPTDBG)
|
||||
message(STATUS "Debug optimization is enabled")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "/Oxt")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm")
|
||||
endif()
|
||||
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()
|
||||
endif()
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
|
||||
endif()
|
||||
|
||||
if(WITH_RUNTIME_DEBUG)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /${RUNTIME_LIBRARY}d")
|
||||
else()
|
||||
@@ -476,8 +491,6 @@ 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)
|
||||
@@ -1346,6 +1359,7 @@ if(WITH_TESTS)
|
||||
db/db_clip_test.cc
|
||||
db/db_dynamic_level_test.cc
|
||||
db/db_encryption_test.cc
|
||||
db/db_etc3_test.cc
|
||||
db/db_flush_test.cc
|
||||
db/db_inplace_update_test.cc
|
||||
db/db_io_failure_test.cc
|
||||
@@ -1439,6 +1453,7 @@ if(WITH_TESTS)
|
||||
table/block_based/block_based_table_reader_test.cc
|
||||
table/block_based/block_test.cc
|
||||
table/block_based/data_block_hash_index_test.cc
|
||||
table/block_based/index_builder_test.cc
|
||||
table/block_based/full_filter_block_test.cc
|
||||
table/block_based/partitioned_filter_block_test.cc
|
||||
table/cleanable_test.cc
|
||||
@@ -1504,6 +1519,7 @@ 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
|
||||
@@ -1614,6 +1630,12 @@ 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)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?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>
|
||||
+97
-7
@@ -1,12 +1,102 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 10.8.3 (11/10/2025)
|
||||
### New Features
|
||||
* Add a GetColumnFamilyMetaData API variant in DB to get the SST files intersecting a given key range.
|
||||
|
||||
## 10.8.2 (11/07/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.
|
||||
* 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.
|
||||
|
||||
## 10.8.1 (10/29/2025)
|
||||
### Behavior Changes
|
||||
* PosixWritableFile now repositions the seek pointer to the new end of file after a call to Truncate.
|
||||
|
||||
## 10.8.0 (10/21/2025)
|
||||
### New Features
|
||||
* Add kFSPrefetch to FSSupportedOps enum to allow file systems to indicate prefetch support capability, avoiding unnecessary prefetch system calls on file systems that don't support them.
|
||||
* Added experimental support `OpenAndCompactOptions::allow_resumption` for resumable compaction that persists progress during `OpenAndCompact()`, allowing interrupted compactions to resume from the last progress persitence. The default behavior is to not persist progress.
|
||||
|
||||
### Public API Changes
|
||||
* Allow specifying output temperature in CompactionOptions
|
||||
* Added `DB::FlushWAL(const FlushWALOptions&)` as an alternative to `DB::FlushWAL(bool sync)`, where `FlushWALOptions` includes a new `rate_limiter_priority` field (default `Env::IO_TOTAL`) that allows rate limiting and priority passing of manual WAL flush's IO operations.
|
||||
* The MultiScan API contract is updated. After a multi scan range got prepared with Prepare API call, the following seeks must seek the start of each prepared scan range in order. In addition, when limit is set, upper bound must be set to the same value of limit before each seek
|
||||
|
||||
### Behavior Changes
|
||||
* `kChangeTemperature` FIFO compaction will now honor `compaction_target_temp` to all levels regardless of `cf_options::last_level_temperature`
|
||||
* Allow UDIs with a non BytewiseComparator
|
||||
|
||||
### Bug Fixes
|
||||
* Fix incorrect MultiScan seek error status due to bugs in handling range limit falling between adjacent SST files key range.
|
||||
* Fix a bug in Page unpinning in MultiScan
|
||||
|
||||
### Performance Improvements
|
||||
* Fixed a performance regression in LZ4 compression that started in version 10.6.0
|
||||
|
||||
## 10.7.0 (09/19/2025)
|
||||
### New Features
|
||||
* Add the fail_if_no_udi_on_open flag in BlockBasedTableOption to control whether a missing user defined index block in a SST is a hard error or not.
|
||||
* 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
|
||||
* Introduce column family option `cf_allow_ingest_behind`. This option aims to replace `DBOptions::allow_ingest_behind` to enable ingest behind at the per-CF level. `DBOptions::allow_ingest_behind` is deprecated.
|
||||
* Introduce `MultiScanArgs::io_coalesce_threshold` to allow a configurable IO coalescing threshold.
|
||||
|
||||
### Public API Changes
|
||||
* `IngestExternalFileOptions::allow_db_generated_files` now allows files ingestion of any DB generated SST file, instead of only the ones with all keys having sequence number 0.
|
||||
* `decouple_partitioned_filters = true` is now the default in BlockBasedTableOptions.
|
||||
* GetTtl() API is now available in TTL DB
|
||||
* Minimum supported version of LZ4 library is now 1.7.0 (r129 from 2015)
|
||||
* Some changes to experimental Compressor and CompressionManager APIs
|
||||
* A new Filesystem::SyncFile function is added for syncing a file that was already written, such as on file ingestion. The default implementation matches previous RocksDB behavior: re-open the file for read-write, sync it, and close it. We recommend overriding for FileSystems that do not require syncing for crash recovery or do not handle (well) re-opening for writes.
|
||||
|
||||
### Behavior Changes
|
||||
* When `allow_ingest_behind` is enabled, compaction will no longer drop tombstones based on the absence of underlying data. Tombstones will be preserved to apply to ingested files.
|
||||
|
||||
### Bug Fixes
|
||||
* Files in dropped column family won't be returned to the caller upon successful, offline MANIFEST iteration in `GetFileChecksumsFromCurrentManifest`.
|
||||
* Fix a bug in MultiScan that causes it to fall back to a normal scan when dictionary compression is enabled.
|
||||
* Fix a crash in iterator Prepare() when fill_cache=false
|
||||
* Fix a bug in MultiScan where incorrect results can be returned when a Scan's range is across multiple files.
|
||||
* Fixed a bug in remote compaction that may mistakenly delete live SST file(s) during the cleanup phase when no keys survive the compaction (all expired)
|
||||
* Allow a user defined index to be configured from a string.
|
||||
* Make the User Defined Index interface consistently use the user key format, fixing the previous mixed usage of internal and user key.
|
||||
|
||||
### Performance Improvements
|
||||
* Small improvement to CPU efficiency of compression using built-in algorithms, and a dramatic efficiency improvement for LZ4HC, based on reusing data structures between invocations.
|
||||
|
||||
## 10.5.0 (07/18/2025)
|
||||
### Public API Changes
|
||||
* 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 occured.
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
@@ -63,7 +153,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 overriden for remote compactions
|
||||
* Added arbitrary string map for additional options to be overridden 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
|
||||
@@ -213,7 +303,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 avaialble any space used
|
||||
* DB::Close now untracks files in SstFileManager, making available any space used
|
||||
by them. Prior to this change they would be orphaned until the DB is re-opened.
|
||||
|
||||
### Bug Fixes
|
||||
@@ -409,7 +499,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.errro.count", "rocksdb.error.handler.bg.io.errro.count", "rocksdb.error.handler.bg.retryable.io.errro.count".
|
||||
* 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".
|
||||
* 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)
|
||||
@@ -436,7 +526,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 explictly 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 explicitly 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.
|
||||
@@ -501,7 +591,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 occured 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 occurred 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.
|
||||
@@ -1289,7 +1379,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 seperately.
|
||||
* Add IncreaseFullHistoryTsLow API so users can advance each column family's full_history_ts_low separately.
|
||||
* 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++17 support (GCC >= 7, Clang >= 5).
|
||||
We do depend on newer gcc/clang with C++20 support (GCC >= 11, Clang >= 10).
|
||||
|
||||
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 7 to get C++17 support.
|
||||
* Upgrade your gcc to version at least 11 to get C++20 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 7 to get C++17 support
|
||||
* Upgrade your gcc to version at least 11 to get C++20 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++ 17:
|
||||
* Install latest C++ compiler that supports C++20:
|
||||
* 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 7 and higher.
|
||||
* Install GCC 11 and higher.
|
||||
* Use these environment variables:
|
||||
|
||||
export CC=gcc
|
||||
|
||||
@@ -148,10 +148,8 @@ 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
|
||||
@@ -638,13 +636,14 @@ 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) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
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 += $(TEST_MAIN_SOURCES) $(TOOL_MAIN_SOURCES) $(BENCH_MAIN_SOURCES)
|
||||
ALL_SOURCES += $(ROCKSDB_PLUGIN_SOURCES) $(ROCKSDB_PLUGIN_TESTS)
|
||||
|
||||
@@ -683,7 +682,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) -I. -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
|
||||
$(CXX) -std=$(or $(ROCKSDB_CXX_STANDARD),c++20) -I. -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
|
||||
|
||||
check-headers: $(HEADER_OK_FILES)
|
||||
|
||||
@@ -1345,6 +1344,9 @@ 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)
|
||||
|
||||
@@ -1425,13 +1427,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 $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
cassandra_format_test: $(OBJ_DIR)/utilities/cassandra/cassandra_format_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cassandra_functional_test: $(OBJ_DIR)/utilities/cassandra/cassandra_functional_test.o $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
cassandra_functional_test: $(OBJ_DIR)/utilities/cassandra/cassandra_functional_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cassandra_row_merge_test: $(OBJ_DIR)/utilities/cassandra/cassandra_row_merge_test.o $(OBJ_DIR)/utilities/cassandra/test_utils.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
cassandra_row_merge_test: $(OBJ_DIR)/utilities/cassandra/cassandra_row_merge_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cassandra_serialize_test: $(OBJ_DIR)/utilities/cassandra/cassandra_serialize_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
@@ -1494,6 +1496,9 @@ db_test: $(OBJ_DIR)/db/db_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
db_test2: $(OBJ_DIR)/db/db_test2.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_etc3_test: $(OBJ_DIR)/db/db_etc3_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
compression_test: $(OBJ_DIR)/util/compression_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1737,6 +1742,9 @@ block_test: $(OBJ_DIR)/table/block_based/block_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
data_block_hash_index_test: $(OBJ_DIR)/table/block_based/data_block_hash_index_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
index_builder_test: $(OBJ_DIR)/table/block_based/index_builder_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
inlineskiplist_test: $(OBJ_DIR)/memtable/inlineskiplist_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1881,6 +1889,9 @@ 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)
|
||||
|
||||
@@ -2251,7 +2262,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 --compile-no-warning-as-error ${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 ${PLATFORM_CMAKE_FLAGS} .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
|
||||
|
||||
lz4-$(LZ4_VER).tar.gz:
|
||||
@@ -2487,6 +2498,8 @@ commit_prereq:
|
||||
false # J=$(J) build_tools/precommit_checker.py unit clang_unit release clang_release tsan asan ubsan lite unit_non_shm
|
||||
# $(MAKE) clean && $(MAKE) jclean && $(MAKE) rocksdbjava;
|
||||
|
||||
FOLLY_COMMIT_HASH = e95383b7c8b5b1e46cf47acf2f317d54f93c8268
|
||||
|
||||
# For public CI runs, checkout folly in a way that can build with RocksDB.
|
||||
# This is mostly intended as a test-only simulation of Meta-internal folly
|
||||
# integration.
|
||||
@@ -2498,7 +2511,7 @@ checkout_folly:
|
||||
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
|
||||
cd third-party/folly && git reset --hard $(FOLLY_COMMIT_HASH)
|
||||
@# Apparently missing include
|
||||
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
|
||||
@# Warning-as-error on memcpy
|
||||
|
||||
@@ -206,6 +206,12 @@ 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",
|
||||
@@ -229,6 +235,12 @@ 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,11 @@ if test -z "$OUTPUT"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# we depend on C++17, but should be compatible with newer standards
|
||||
# we depend on C++20, but should be compatible with newer standards
|
||||
if [ "$ROCKSDB_CXX_STANDARD" ]; then
|
||||
PLATFORM_CXXFLAGS="-std=$ROCKSDB_CXX_STANDARD"
|
||||
else
|
||||
PLATFORM_CXXFLAGS="-std=c++17"
|
||||
PLATFORM_CXXFLAGS="-std=c++20"
|
||||
fi
|
||||
|
||||
# we currently depend on POSIX platform
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
# 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/*
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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
+74
-8
@@ -184,6 +184,11 @@ 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 {
|
||||
|
||||
@@ -387,7 +392,12 @@ 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;
|
||||
@@ -406,12 +416,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" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
if (FLAGS_cache_type == "fixed_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") {
|
||||
} else if (FLAGS_cache_type == "auto_hyper_clock_cache" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
if (FLAGS_value_bytes_estimate > 0) {
|
||||
opts.min_avg_entry_charge = FLAGS_value_bytes_estimate;
|
||||
}
|
||||
@@ -420,7 +430,7 @@ class CacheBench {
|
||||
exit(1);
|
||||
}
|
||||
ConfigureSecondaryCache(opts);
|
||||
cache_ = opts.MakeSharedCache();
|
||||
return opts.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "lru_cache") {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
|
||||
false /* strict_capacity_limit */,
|
||||
@@ -428,15 +438,13 @@ class CacheBench {
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
opts.memory_allocator = allocator;
|
||||
ConfigureSecondaryCache(opts);
|
||||
cache_ = NewLRUCache(opts);
|
||||
return NewLRUCache(opts);
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
~CacheBench() = default;
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(FLAGS_seed);
|
||||
KeyGen keygen;
|
||||
@@ -490,7 +498,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();
|
||||
@@ -1152,6 +1160,59 @@ 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);
|
||||
@@ -1162,6 +1223,11 @@ 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
+88
-75
@@ -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,10 +26,9 @@
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "port/lang.h"
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/random.h"
|
||||
@@ -361,16 +360,9 @@ void ConstApplyToEntriesRange(const Func& func, const HandleImpl* begin,
|
||||
}
|
||||
}
|
||||
|
||||
constexpr uint32_t kStrictCapacityLimitBit = 1u << 31;
|
||||
|
||||
uint32_t SanitizeEncodeEecAndScl(int eviction_effort_cap,
|
||||
bool strict_capacit_limit) {
|
||||
uint32_t SanitizeEvictionEffortCap(int eviction_effort_cap) {
|
||||
eviction_effort_cap = std::max(int{1}, 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;
|
||||
return static_cast<uint32_t>(eviction_effort_cap);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -381,6 +373,22 @@ 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) {
|
||||
@@ -402,8 +410,7 @@ HandleImpl* BaseClockTable::StandaloneInsert(
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
ClockHandleBasicData& proto, size_t capacity, uint32_t eec_and_scl,
|
||||
bool allow_uncharged) {
|
||||
ClockHandleBasicData& proto, bool allow_uncharged) {
|
||||
Table& derived = static_cast<Table&>(*this);
|
||||
typename Table::InsertState state;
|
||||
derived.StartInsert(state);
|
||||
@@ -412,10 +419,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 & kStrictCapacityLimitBit) {
|
||||
if (eec_and_scl_.LoadRelaxed().Get<StrictCapacityLimit>()) {
|
||||
Status s = ChargeUsageMaybeEvictStrict<Table>(
|
||||
total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false, eec_and_scl, state);
|
||||
total_charge,
|
||||
/*need_evict_for_occupancy=*/false, state);
|
||||
if (!s.ok()) {
|
||||
if (allow_uncharged) {
|
||||
proto.total_charge = 0;
|
||||
@@ -426,8 +433,8 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
|
||||
total_charge, capacity,
|
||||
/*need_evict_for_occupancy=*/false, eec_and_scl, state);
|
||||
total_charge,
|
||||
/*need_evict_for_occupancy=*/false, state);
|
||||
if (!success) {
|
||||
// Force the issue
|
||||
usage_.FetchAddRelaxed(total_charge);
|
||||
@@ -439,8 +446,9 @@ typename Table::HandleImpl* BaseClockTable::CreateStandalone(
|
||||
|
||||
template <class Table>
|
||||
Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap, typename Table::InsertState& state) {
|
||||
size_t total_charge, bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state) {
|
||||
const size_t capacity = capacity_.LoadRelaxed();
|
||||
if (total_charge > capacity) {
|
||||
return Status::MemoryLimit(
|
||||
"Cache entry too large for a single cache shard: " +
|
||||
@@ -465,8 +473,7 @@ Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
}
|
||||
if (request_evict_charge > 0) {
|
||||
EvictionData data;
|
||||
static_cast<Table*>(this)->Evict(request_evict_charge, state, &data,
|
||||
eviction_effort_cap);
|
||||
static_cast<Table*>(this)->Evict(request_evict_charge, state, &data);
|
||||
occupancy_.FetchSub(data.freed_count);
|
||||
if (LIKELY(data.freed_charge > need_evict_charge)) {
|
||||
assert(data.freed_count > 0);
|
||||
@@ -495,8 +502,8 @@ Status BaseClockTable::ChargeUsageMaybeEvictStrict(
|
||||
|
||||
template <class Table>
|
||||
inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
size_t total_charge, size_t capacity, bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap, typename Table::InsertState& state) {
|
||||
size_t total_charge, bool need_evict_for_occupancy,
|
||||
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
|
||||
@@ -506,7 +513,8 @@ 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.
|
||||
size_t old_usage = usage_.LoadRelaxed();
|
||||
const size_t old_usage = usage_.LoadRelaxed();
|
||||
const size_t capacity = capacity_.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,
|
||||
@@ -532,8 +540,7 @@ inline bool BaseClockTable::ChargeUsageMaybeEvictNonStrict(
|
||||
}
|
||||
EvictionData data;
|
||||
if (need_evict_charge > 0) {
|
||||
static_cast<Table*>(this)->Evict(need_evict_charge, state, &data,
|
||||
eviction_effort_cap);
|
||||
static_cast<Table*>(this)->Evict(need_evict_charge, state, &data);
|
||||
// Deal with potential occupancy deficit
|
||||
if (UNLIKELY(need_evict_for_occupancy) && data.freed_count == 0) {
|
||||
assert(data.freed_charge == 0);
|
||||
@@ -569,8 +576,10 @@ void BaseClockTable::TrackAndReleaseEvictedEntry(ClockHandle* h) {
|
||||
MarkEmpty(*h);
|
||||
}
|
||||
|
||||
bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
bool BaseClockTable::IsEvictionEffortExceeded(
|
||||
const BaseClockTable::EvictionData& data) const {
|
||||
auto eviction_effort_cap =
|
||||
eec_and_scl_.LoadRelaxed().GetEffectiveEvictionEffortCap();
|
||||
// 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.
|
||||
@@ -581,8 +590,7 @@ bool IsEvictionEffortExceeded(const BaseClockTable::EvictionData& data,
|
||||
template <class Table>
|
||||
Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
uint32_t eec_and_scl) {
|
||||
Cache::Priority priority) {
|
||||
using HandleImpl = typename Table::HandleImpl;
|
||||
Table& derived = static_cast<Table&>(*this);
|
||||
|
||||
@@ -603,9 +611,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 & kStrictCapacityLimitBit) {
|
||||
if (eec_and_scl_.LoadRelaxed().Get<StrictCapacityLimit>()) {
|
||||
Status s = ChargeUsageMaybeEvictStrict<Table>(
|
||||
total_charge, capacity, need_evict_for_occupancy, eec_and_scl, state);
|
||||
total_charge, need_evict_for_occupancy, state);
|
||||
if (!s.ok()) {
|
||||
// Revert occupancy
|
||||
occupancy_.FetchSubRelaxed(1);
|
||||
@@ -614,7 +622,7 @@ Status BaseClockTable::Insert(const ClockHandleBasicData& proto,
|
||||
} else {
|
||||
// Case strict_capacity_limit == false
|
||||
bool success = ChargeUsageMaybeEvictNonStrict<Table>(
|
||||
total_charge, capacity, need_evict_for_occupancy, eec_and_scl, state);
|
||||
total_charge, need_evict_for_occupancy, state);
|
||||
if (!success) {
|
||||
// Revert occupancy
|
||||
occupancy_.FetchSubRelaxed(1);
|
||||
@@ -718,11 +726,13 @@ void BaseClockTable::TEST_ReleaseNMinus1(ClockHandle* h, size_t n) {
|
||||
#endif
|
||||
|
||||
FixedHyperClockTable::FixedHyperClockTable(
|
||||
size_t capacity, CacheMetadataChargePolicy metadata_charge_policy,
|
||||
size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
: BaseClockTable(metadata_charge_policy, allocator, eviction_callback,
|
||||
: BaseClockTable(capacity, strict_capacity_limit, opts.eviction_effort_cap,
|
||||
metadata_charge_policy, allocator, eviction_callback,
|
||||
hash_seed),
|
||||
length_bits_(CalcHashBits(capacity, opts.estimated_value_size,
|
||||
metadata_charge_policy)),
|
||||
@@ -1113,8 +1123,7 @@ inline void FixedHyperClockTable::ReclaimEntryUsage(size_t total_charge) {
|
||||
}
|
||||
|
||||
inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
|
||||
EvictionData* data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
EvictionData* data) {
|
||||
// precondition
|
||||
assert(requested_charge > 0);
|
||||
|
||||
@@ -1149,7 +1158,7 @@ inline void FixedHyperClockTable::Evict(size_t requested_charge, InsertState&,
|
||||
if (old_clock_pointer >= max_clock_pointer) {
|
||||
return;
|
||||
}
|
||||
if (IsEvictionEffortExceeded(*data, eviction_effort_cap)) {
|
||||
if (IsEvictionEffortExceeded(*data)) {
|
||||
eviction_effort_exceeded_count_.FetchAddRelaxed(1);
|
||||
return;
|
||||
}
|
||||
@@ -1167,14 +1176,11 @@ ClockCacheShard<Table>::ClockCacheShard(
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const typename Table::Opts& opts)
|
||||
: CacheShardBase(metadata_charge_policy),
|
||||
table_(capacity, metadata_charge_policy, allocator, eviction_callback,
|
||||
hash_seed, opts),
|
||||
capacity_(capacity),
|
||||
eec_and_scl_(SanitizeEncodeEecAndScl(opts.eviction_effort_cap,
|
||||
strict_capacity_limit)) {
|
||||
table_(capacity, strict_capacity_limit, metadata_charge_policy, allocator,
|
||||
eviction_callback, hash_seed, opts) {
|
||||
// Initial charge metadata should not exceed capacity
|
||||
assert(table_.GetUsage() <= capacity_.LoadRelaxed() ||
|
||||
capacity_.LoadRelaxed() < sizeof(HandleImpl));
|
||||
assert(table_.GetUsage() <= table_.GetCapacity() ||
|
||||
table_.GetCapacity() < sizeof(HandleImpl));
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1240,18 +1246,14 @@ int FixedHyperClockTable::CalcHashBits(
|
||||
|
||||
template <class Table>
|
||||
void ClockCacheShard<Table>::SetCapacity(size_t capacity) {
|
||||
capacity_.StoreRelaxed(capacity);
|
||||
table_.SetCapacity(capacity);
|
||||
// next Insert will take care of any necessary evictions
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
void ClockCacheShard<Table>::SetStrictCapacityLimit(
|
||||
bool strict_capacity_limit) {
|
||||
if (strict_capacity_limit) {
|
||||
eec_and_scl_.FetchOrRelaxed(kStrictCapacityLimitBit);
|
||||
} else {
|
||||
eec_and_scl_.FetchAndRelaxed(~kStrictCapacityLimitBit);
|
||||
}
|
||||
table_.SetStrictCapacityLimit(strict_capacity_limit);
|
||||
// next Insert will take care of any necessary evictions
|
||||
}
|
||||
|
||||
@@ -1271,9 +1273,7 @@ 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,
|
||||
capacity_.LoadRelaxed(),
|
||||
eec_and_scl_.LoadRelaxed());
|
||||
return table_.template Insert<Table>(proto, handle, priority);
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1288,9 +1288,7 @@ typename Table::HandleImpl* ClockCacheShard<Table>::CreateStandalone(
|
||||
proto.value = obj;
|
||||
proto.helper = helper;
|
||||
proto.total_charge = charge;
|
||||
return table_.template CreateStandalone<Table>(proto, capacity_.LoadRelaxed(),
|
||||
eec_and_scl_.LoadRelaxed(),
|
||||
allow_uncharged);
|
||||
return table_.template CreateStandalone<Table>(proto, allow_uncharged);
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1359,7 +1357,7 @@ size_t ClockCacheShard<Table>::GetStandaloneUsage() const {
|
||||
|
||||
template <class Table>
|
||||
size_t ClockCacheShard<Table>::GetCapacity() const {
|
||||
return capacity_.LoadRelaxed();
|
||||
return table_.GetCapacity();
|
||||
}
|
||||
|
||||
template <class Table>
|
||||
@@ -1727,10 +1725,13 @@ 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 > port::kPageSize) {
|
||||
if (capacity > kPresumedPageSize) {
|
||||
// Start with one memory page
|
||||
return port::kPageSize / sizeof(AutoHyperClockTable::HandleImpl);
|
||||
return kPresumedPageSize / sizeof(AutoHyperClockTable::HandleImpl);
|
||||
} else {
|
||||
// Mostly to make unit tests happy
|
||||
return 4;
|
||||
@@ -1969,11 +1970,13 @@ class AutoHyperClockTable::ChainRewriteLock {
|
||||
};
|
||||
|
||||
AutoHyperClockTable::AutoHyperClockTable(
|
||||
size_t capacity, CacheMetadataChargePolicy metadata_charge_policy,
|
||||
size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback, const uint32_t* hash_seed,
|
||||
const Opts& opts)
|
||||
: BaseClockTable(metadata_charge_policy, allocator, eviction_callback,
|
||||
: BaseClockTable(capacity, strict_capacity_limit, opts.eviction_effort_cap,
|
||||
metadata_charge_policy, allocator, eviction_callback,
|
||||
hash_seed),
|
||||
array_(MemMapping::AllocateLazyZeroed(
|
||||
sizeof(HandleImpl) * CalcMaxUsableLength(capacity,
|
||||
@@ -1985,6 +1988,11 @@ 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
|
||||
@@ -2052,15 +2060,21 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
HandleImpl::kUnusedMarker) {
|
||||
used_end++;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = used_end; i < array_.Count(); i++) {
|
||||
// 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() == 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
|
||||
#endif // !NDEBUG
|
||||
for (size_t i = 0; i < used_end; i++) {
|
||||
HandleImpl& h = array_[i];
|
||||
switch (h.meta.LoadRelaxed() >> ClockHandle::kStateShift) {
|
||||
@@ -2083,7 +2097,7 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
assert(!was_pointed_to[next]);
|
||||
was_pointed_to[next] = true;
|
||||
}
|
||||
#endif
|
||||
#endif // !NDEBUG
|
||||
break;
|
||||
// otherwise
|
||||
default:
|
||||
@@ -2097,7 +2111,7 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
assert(!was_pointed_to[next]);
|
||||
was_pointed_to[next] = true;
|
||||
}
|
||||
#endif
|
||||
#endif // !NDEBUG
|
||||
}
|
||||
#ifndef NDEBUG // Extra invariant checking
|
||||
// This check is not perfect, but should detect most reasonable cases
|
||||
@@ -2110,7 +2124,7 @@ AutoHyperClockTable::~AutoHyperClockTable() {
|
||||
assert(!was_pointed_to[i]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // !NDEBUG
|
||||
|
||||
// Metadata charging only follows the published table size
|
||||
assert(usage_.LoadRelaxed() == 0 ||
|
||||
@@ -3468,8 +3482,7 @@ void AutoHyperClockTable::EraseUnRefEntries() {
|
||||
}
|
||||
|
||||
void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
|
||||
EvictionData* data,
|
||||
uint32_t eviction_effort_cap) {
|
||||
EvictionData* data) {
|
||||
// precondition
|
||||
assert(requested_charge > 0);
|
||||
|
||||
@@ -3561,7 +3574,7 @@ void AutoHyperClockTable::Evict(size_t requested_charge, InsertState& state,
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsEvictionEffortExceeded(*data, eviction_effort_cap)) {
|
||||
if (IsEvictionEffortExceeded(*data)) {
|
||||
eviction_effort_exceeded_count_.FetchAddRelaxed(1);
|
||||
return;
|
||||
}
|
||||
@@ -3579,7 +3592,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 = port::kPageSize / sizeof(HandleImpl);
|
||||
const size_t slots_per_page = kPresumedPageSize / sizeof(HandleImpl);
|
||||
|
||||
// Round up to page size
|
||||
return ((num_slots + slots_per_page - 1) / slots_per_page) * slots_per_page;
|
||||
|
||||
Vendored
+44
-36
@@ -9,8 +9,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -19,14 +17,10 @@
|
||||
|
||||
#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/autovector.h"
|
||||
#include "util/bit_fields.h"
|
||||
#include "util/math.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -383,25 +377,20 @@ class BaseClockTable {
|
||||
int eviction_effort_cap;
|
||||
};
|
||||
|
||||
BaseClockTable(CacheMetadataChargePolicy metadata_charge_policy,
|
||||
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)
|
||||
: metadata_charge_policy_(metadata_charge_policy),
|
||||
allocator_(allocator),
|
||||
eviction_callback_(*eviction_callback),
|
||||
hash_seed_(*hash_seed) {}
|
||||
const uint32_t* 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,
|
||||
size_t capacity, uint32_t eec_and_scl);
|
||||
typename Table::HandleImpl** handle, Cache::Priority priority);
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
@@ -411,6 +400,18 @@ 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(); }
|
||||
@@ -427,6 +428,7 @@ 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);
|
||||
@@ -448,9 +450,8 @@ 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, size_t capacity,
|
||||
Status ChargeUsageMaybeEvictStrict(size_t total_charge,
|
||||
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`
|
||||
@@ -462,9 +463,8 @@ class BaseClockTable {
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge, size_t capacity,
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge,
|
||||
bool need_evict_for_occupancy,
|
||||
uint32_t eviction_effort_cap,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
protected: // data
|
||||
@@ -497,6 +497,25 @@ 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_;
|
||||
|
||||
@@ -551,7 +570,7 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
size_t estimated_value_size;
|
||||
};
|
||||
|
||||
FixedHyperClockTable(size_t capacity,
|
||||
FixedHyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
@@ -573,8 +592,7 @@ 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,
|
||||
uint32_t eviction_effort_cap);
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
@@ -841,7 +859,7 @@ class AutoHyperClockTable : public BaseClockTable {
|
||||
size_t min_avg_value_size;
|
||||
};
|
||||
|
||||
AutoHyperClockTable(size_t capacity,
|
||||
AutoHyperClockTable(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
@@ -868,8 +886,7 @@ 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,
|
||||
uint32_t eviction_effort_cap);
|
||||
void Evict(size_t requested_charge, InsertState& state, EvictionData* data);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
@@ -1102,15 +1119,6 @@ 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 + teh secondary cache budget, as follows -
|
||||
// budget + the 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 refelcted here by releasing an equal amount
|
||||
// actual usage to shrink, it is reflected 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 seconary components. However, this would
|
||||
// reservation into primary and secondary 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;
|
||||
size_t old_sec_capacity = 0;
|
||||
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 relect lower secondary
|
||||
// 2. Decrease pri_cache_res_ reservation to reflect 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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ccache.exe cl.exe %*
|
||||
@@ -342,6 +342,9 @@ 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;
|
||||
|
||||
@@ -82,6 +82,7 @@ using ROCKSDB_NAMESPACE::DbPath;
|
||||
using ROCKSDB_NAMESPACE::Env;
|
||||
using ROCKSDB_NAMESPACE::EnvOptions;
|
||||
using ROCKSDB_NAMESPACE::EventListener;
|
||||
using ROCKSDB_NAMESPACE::ExportImportFilesMetaData;
|
||||
using ROCKSDB_NAMESPACE::ExternalFileIngestionInfo;
|
||||
using ROCKSDB_NAMESPACE::FileLock;
|
||||
using ROCKSDB_NAMESPACE::FilterPolicy;
|
||||
@@ -89,6 +90,7 @@ using ROCKSDB_NAMESPACE::FlushJobInfo;
|
||||
using ROCKSDB_NAMESPACE::FlushOptions;
|
||||
using ROCKSDB_NAMESPACE::HistogramData;
|
||||
using ROCKSDB_NAMESPACE::HyperClockCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::ImportColumnFamilyOptions;
|
||||
using ROCKSDB_NAMESPACE::InfoLogLevel;
|
||||
using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
|
||||
using ROCKSDB_NAMESPACE::Iterator;
|
||||
@@ -245,6 +247,9 @@ struct rocksdb_write_buffer_manager_t {
|
||||
struct rocksdb_sst_file_manager_t {
|
||||
std::shared_ptr<SstFileManager> rep;
|
||||
};
|
||||
struct rocksdb_livefile_t {
|
||||
LiveFileMetaData rep;
|
||||
};
|
||||
struct rocksdb_livefiles_t {
|
||||
std::vector<LiveFileMetaData> rep;
|
||||
};
|
||||
@@ -255,6 +260,12 @@ struct rocksdb_column_family_handle_t {
|
||||
struct rocksdb_column_family_metadata_t {
|
||||
ColumnFamilyMetaData rep;
|
||||
};
|
||||
struct rocksdb_export_import_files_metadata_t {
|
||||
ExportImportFilesMetaData* rep;
|
||||
};
|
||||
struct rocksdb_import_column_family_options_t {
|
||||
ImportColumnFamilyOptions rep;
|
||||
};
|
||||
struct rocksdb_level_metadata_t {
|
||||
const LevelMetaData* rep;
|
||||
};
|
||||
@@ -947,6 +958,22 @@ void rocksdb_checkpoint_create(rocksdb_checkpoint_t* checkpoint,
|
||||
std::string(checkpoint_dir), log_size_for_flush));
|
||||
}
|
||||
|
||||
rocksdb_export_import_files_metadata_t* rocksdb_checkpoint_export_column_family(
|
||||
rocksdb_checkpoint_t* checkpoint,
|
||||
rocksdb_column_family_handle_t* column_family, const char* export_dir,
|
||||
char** errptr) {
|
||||
ExportImportFilesMetaData* metadata = nullptr;
|
||||
if (SaveError(errptr,
|
||||
checkpoint->rep->ExportColumnFamily(
|
||||
column_family->rep, std::string(export_dir), &metadata))) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_export_import_files_metadata_t* result =
|
||||
new rocksdb_export_import_files_metadata_t;
|
||||
result->rep = metadata;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_checkpoint_object_destroy(rocksdb_checkpoint_t* checkpoint) {
|
||||
delete checkpoint->rep;
|
||||
delete checkpoint;
|
||||
@@ -1190,6 +1217,26 @@ rocksdb_column_family_handle_t** rocksdb_create_column_families(
|
||||
return c_handles;
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t* rocksdb_create_column_family_with_import(
|
||||
rocksdb_t* db, rocksdb_options_t* column_family_options,
|
||||
const char* column_family_name,
|
||||
rocksdb_import_column_family_options_t* import_options,
|
||||
rocksdb_export_import_files_metadata_t* export_import_files_metadata,
|
||||
char** errptr) {
|
||||
rocksdb_column_family_handle_t* handle = new rocksdb_column_family_handle_t;
|
||||
handle->rep = nullptr;
|
||||
if (SaveError(errptr,
|
||||
db->rep->CreateColumnFamilyWithImport(
|
||||
ColumnFamilyOptions(column_family_options->rep),
|
||||
std::string(column_family_name), import_options->rep,
|
||||
*(export_import_files_metadata->rep), &(handle->rep)))) {
|
||||
delete handle;
|
||||
return nullptr;
|
||||
}
|
||||
handle->immortal = false;
|
||||
return handle;
|
||||
}
|
||||
|
||||
void rocksdb_create_column_families_destroy(
|
||||
rocksdb_column_family_handle_t** list) {
|
||||
free(list);
|
||||
@@ -6209,6 +6256,10 @@ void rocksdb_options_set_min_level_to_compress(rocksdb_options_t* opt,
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_livefiles_t* rocksdb_livefiles_create() {
|
||||
return new rocksdb_livefiles_t;
|
||||
}
|
||||
|
||||
int rocksdb_livefiles_count(const rocksdb_livefiles_t* lf) {
|
||||
return static_cast<int>(lf->rep.size());
|
||||
}
|
||||
@@ -6222,6 +6273,16 @@ const char* rocksdb_livefiles_name(const rocksdb_livefiles_t* lf, int index) {
|
||||
return lf->rep[index].name.c_str();
|
||||
}
|
||||
|
||||
const char* rocksdb_livefiles_directory(const rocksdb_livefiles_t* lf,
|
||||
int index) {
|
||||
if (lf->rep[index].directory.empty()) {
|
||||
// db_path is deprecated but still returned by some code paths
|
||||
return lf->rep[index].db_path.c_str();
|
||||
} else {
|
||||
return lf->rep[index].directory.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
int rocksdb_livefiles_level(const rocksdb_livefiles_t* lf, int index) {
|
||||
return lf->rep[index].level;
|
||||
}
|
||||
@@ -6242,6 +6303,16 @@ const char* rocksdb_livefiles_largestkey(const rocksdb_livefiles_t* lf,
|
||||
return lf->rep[index].largestkey.data();
|
||||
}
|
||||
|
||||
uint64_t rocksdb_livefiles_smallest_seqno(const rocksdb_livefiles_t* lf,
|
||||
int index) {
|
||||
return lf->rep[index].smallest_seqno;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_livefiles_largest_seqno(const rocksdb_livefiles_t* lf,
|
||||
int index) {
|
||||
return lf->rep[index].largest_seqno;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_livefiles_entries(const rocksdb_livefiles_t* lf, int index) {
|
||||
return lf->rep[index].num_entries;
|
||||
}
|
||||
@@ -6252,6 +6323,71 @@ uint64_t rocksdb_livefiles_deletions(const rocksdb_livefiles_t* lf, int index) {
|
||||
|
||||
void rocksdb_livefiles_destroy(const rocksdb_livefiles_t* lf) { delete lf; }
|
||||
|
||||
rocksdb_livefile_t* rocksdb_livefile_create() { return new rocksdb_livefile_t; }
|
||||
|
||||
void rocksdb_livefile_set_column_family_name(rocksdb_livefile_t* lf,
|
||||
const char* column_family_name) {
|
||||
lf->rep.column_family_name = std::string(column_family_name);
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_level(rocksdb_livefile_t* lf, int level) {
|
||||
lf->rep.level = level;
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_name(rocksdb_livefile_t* lf, const char* name) {
|
||||
lf->rep.name = std::string(name);
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_directory(rocksdb_livefile_t* lf,
|
||||
const char* directory) {
|
||||
lf->rep.directory = std::string(directory);
|
||||
lf->rep.db_path = std::string(directory); // deprecated but still needed
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_size(rocksdb_livefile_t* lf, size_t size) {
|
||||
lf->rep.size = size;
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_smallest_key(rocksdb_livefile_t* lf,
|
||||
const char* smallest_key,
|
||||
size_t smallest_key_len) {
|
||||
lf->rep.smallestkey = std::string(smallest_key, smallest_key_len);
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_largest_key(rocksdb_livefile_t* lf,
|
||||
const char* largest_key,
|
||||
size_t largest_key_len) {
|
||||
lf->rep.largestkey = std::string(largest_key, largest_key_len);
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_smallest_seqno(rocksdb_livefile_t* lf,
|
||||
uint64_t smallest_seqno) {
|
||||
lf->rep.smallest_seqno = smallest_seqno;
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_largest_seqno(rocksdb_livefile_t* lf,
|
||||
uint64_t largest_seqno) {
|
||||
lf->rep.largest_seqno = largest_seqno;
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_num_entries(rocksdb_livefile_t* lf,
|
||||
uint64_t num_entries) {
|
||||
lf->rep.num_entries = num_entries;
|
||||
}
|
||||
|
||||
void rocksdb_livefile_set_num_deletions(rocksdb_livefile_t* lf,
|
||||
uint64_t num_deletions) {
|
||||
lf->rep.num_deletions = num_deletions;
|
||||
}
|
||||
|
||||
void rocksdb_livefile_destroy(rocksdb_livefile_t* lf) { delete lf; }
|
||||
|
||||
void rocksdb_livefiles_add(rocksdb_livefiles_t* lf,
|
||||
rocksdb_livefile_t* livefile) {
|
||||
lf->rep.push_back(std::move(livefile->rep));
|
||||
delete livefile;
|
||||
}
|
||||
|
||||
void rocksdb_get_options_from_string(const rocksdb_options_t* base_options,
|
||||
const char* opts_str,
|
||||
rocksdb_options_t* new_options,
|
||||
@@ -6402,6 +6538,58 @@ char* rocksdb_sst_file_metadata_get_largestkey(
|
||||
return CopyString(file_meta->rep->largestkey);
|
||||
}
|
||||
|
||||
rocksdb_import_column_family_options_t*
|
||||
rocksdb_import_column_family_options_create() {
|
||||
return new rocksdb_import_column_family_options_t;
|
||||
}
|
||||
|
||||
void rocksdb_import_column_family_options_set_move_files(
|
||||
rocksdb_import_column_family_options_t* opt, unsigned char v) {
|
||||
opt->rep.move_files = v;
|
||||
}
|
||||
|
||||
void rocksdb_import_column_family_options_destroy(
|
||||
rocksdb_import_column_family_options_t* metadata) {
|
||||
delete metadata;
|
||||
}
|
||||
|
||||
rocksdb_export_import_files_metadata_t*
|
||||
rocksdb_export_import_files_metadata_create() {
|
||||
auto metadata = new rocksdb_export_import_files_metadata_t;
|
||||
metadata->rep = new ExportImportFilesMetaData;
|
||||
return metadata;
|
||||
}
|
||||
|
||||
char* rocksdb_export_import_files_metadata_get_db_comparator_name(
|
||||
rocksdb_export_import_files_metadata_t* metadata) {
|
||||
return strdup(metadata->rep->db_comparator_name.c_str());
|
||||
}
|
||||
|
||||
void rocksdb_export_import_files_metadata_set_db_comparator_name(
|
||||
rocksdb_export_import_files_metadata_t* metadata, const char* name) {
|
||||
metadata->rep->db_comparator_name = std::string(name);
|
||||
}
|
||||
|
||||
rocksdb_livefiles_t* rocksdb_export_import_files_metadata_get_files(
|
||||
rocksdb_export_import_files_metadata_t* export_import_metadata) {
|
||||
auto files = new rocksdb_livefiles_t;
|
||||
files->rep = std::vector(export_import_metadata->rep->files);
|
||||
return files;
|
||||
}
|
||||
|
||||
void rocksdb_export_import_files_metadata_set_files(
|
||||
rocksdb_export_import_files_metadata_t* metadata,
|
||||
rocksdb_livefiles_t* files) {
|
||||
metadata->rep->files = std::move(files->rep);
|
||||
delete files;
|
||||
}
|
||||
|
||||
void rocksdb_export_import_files_metadata_destroy(
|
||||
rocksdb_export_import_files_metadata_t* metadata) {
|
||||
delete metadata->rep;
|
||||
delete metadata;
|
||||
}
|
||||
|
||||
/* Transactions */
|
||||
|
||||
rocksdb_transactiondb_options_t* rocksdb_transactiondb_options_create() {
|
||||
@@ -6433,6 +6621,11 @@ void rocksdb_transactiondb_options_set_default_lock_timeout(
|
||||
opt->rep.default_lock_timeout = default_lock_timeout;
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_options_set_use_per_key_point_lock_mgr(
|
||||
rocksdb_transactiondb_options_t* opt, int use_per_key_point_lock_mgr) {
|
||||
opt->rep.use_per_key_point_lock_mgr = use_per_key_point_lock_mgr;
|
||||
}
|
||||
|
||||
rocksdb_transaction_options_t* rocksdb_transaction_options_create() {
|
||||
return new rocksdb_transaction_options_t;
|
||||
}
|
||||
|
||||
+72
@@ -1036,6 +1036,78 @@ 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");
|
||||
|
||||
@@ -534,6 +534,232 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
// Helper function to generate zero-padded keys
|
||||
// e.g., MakeKey("a", 5) -> "a05", MakeKey("b", 42) -> "b42"
|
||||
static std::string MakeKey(const std::string& prefix, int index) {
|
||||
return prefix + (index < 10 ? "0" : "") + std::to_string(index);
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, TrivialMoveNonOverlappingFiles) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
|
||||
DB* db = nullptr;
|
||||
ASSERT_OK(DestroyDB(db_name_, options));
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(db, nullptr);
|
||||
|
||||
// Create 3 non-overlapping files in L0
|
||||
// File 1: keys [a00-a99]
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("a", i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// File 2: keys [b00-b99]
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("b", i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// File 3: keys [c00-c99]
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("c", i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Verify files are in L0
|
||||
ColumnFamilyMetaData meta;
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[0].files.size(), 3);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 0);
|
||||
|
||||
// Get L0 files
|
||||
std::vector<std::string> l0_files;
|
||||
for (const auto& file : meta.levels[0].files) {
|
||||
l0_files.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
|
||||
CompactionOptions compact_option;
|
||||
compact_option.allow_trivial_move = true;
|
||||
// Compact all L0 files to L1 (non-overlapping in L1)
|
||||
ASSERT_OK(db->CompactFiles(compact_option, l0_files, 1));
|
||||
|
||||
// Verify files are now in L1
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[0].files.size(), 0);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 3);
|
||||
|
||||
// Get the first file from L1 (should be the one with keys a00-a99)
|
||||
std::string l1_file_to_move;
|
||||
std::vector<std::string> l1_files_to_move_later;
|
||||
uint64_t l1_file_number = 0;
|
||||
for (const auto& file : meta.levels[1].files) {
|
||||
if (file.smallestkey[0] == 'a') {
|
||||
l1_file_to_move = file.db_path + "/" + file.name;
|
||||
l1_file_number = file.file_number;
|
||||
} else {
|
||||
l1_files_to_move_later.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
}
|
||||
ASSERT_FALSE(l1_file_to_move.empty());
|
||||
|
||||
// Set up sync point to verify trivial move path is taken
|
||||
bool trivial_move_executed = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::CompactFilesImpl:TrivialMove",
|
||||
[&](void* /*arg*/) { trivial_move_executed = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Move the file from L1 to L6 - this should be a trivial move
|
||||
// because the file doesn't overlap with anything in L6
|
||||
std::vector<std::string> files_to_move = {l1_file_to_move};
|
||||
ASSERT_OK(db->CompactFiles(compact_option, files_to_move, 6));
|
||||
|
||||
// Verify trivial move was executed
|
||||
ASSERT_TRUE(trivial_move_executed);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// Verify the file is now in L6
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 2); // Two files remain in L1
|
||||
ASSERT_EQ(meta.levels[6].files.size(), 1); // One file in L6
|
||||
|
||||
// Verify it's the correct file in L6
|
||||
bool found_file_in_l6 = false;
|
||||
for (const auto& file : meta.levels[6].files) {
|
||||
if (file.file_number == l1_file_number) {
|
||||
found_file_in_l6 = true;
|
||||
// Verify key range hasn't changed
|
||||
ASSERT_EQ(file.smallestkey[0], 'a');
|
||||
ASSERT_EQ(file.largestkey[0], 'a');
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(found_file_in_l6);
|
||||
|
||||
// Move the other 2 files from L1 to L6, with allow_trivial_move set to false.
|
||||
// This will trigger a normal compaction, so the 2 files will be compacted
|
||||
// into a single file in L6.
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l1_files_to_move_later, 6));
|
||||
|
||||
// Verify files in L6
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 0); // Zero files remain in L1
|
||||
ASSERT_EQ(meta.levels[6].files.size(), 2); // Two file in L6
|
||||
|
||||
// Verify data integrity - all keys should still be readable
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("a", i);
|
||||
std::string value;
|
||||
ASSERT_OK(db->Get(ReadOptions(), key, &value));
|
||||
ASSERT_EQ(value, "value_" + key);
|
||||
}
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, TrivialMoveBlockedByOverlap) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.num_levels = 7;
|
||||
|
||||
DB* db = nullptr;
|
||||
ASSERT_OK(DestroyDB(db_name_, options));
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(db, nullptr);
|
||||
|
||||
// Create a file in L6 with keys [m00-m99] (wide range)
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string key = MakeKey("m", i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Get L0 file
|
||||
ColumnFamilyMetaData meta;
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
std::vector<std::string> l0_files;
|
||||
for (const auto& file : meta.levels[0].files) {
|
||||
l0_files.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
|
||||
CompactionOptions compact_option;
|
||||
compact_option.allow_trivial_move = true;
|
||||
|
||||
// Move to L6
|
||||
ASSERT_OK(db->CompactFiles(compact_option, l0_files, 6));
|
||||
|
||||
// Now create a file in L1 with overlapping keys [m50-m60]
|
||||
for (int i = 50; i <= 60; i++) {
|
||||
std::string key = "m" + std::to_string(i);
|
||||
ASSERT_OK(db->Put(WriteOptions(), key, "updated_value_" + key));
|
||||
}
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Get the L0 file
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
std::vector<std::string> l0_files_2;
|
||||
for (const auto& file : meta.levels[0].files) {
|
||||
l0_files_2.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
|
||||
// Move to L1
|
||||
ASSERT_OK(db->CompactFiles(compact_option, l0_files_2, 1));
|
||||
|
||||
// Get the L1 file
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 1);
|
||||
std::string l1_file =
|
||||
meta.levels[1].files[0].db_path + "/" + meta.levels[1].files[0].name;
|
||||
|
||||
// Set up sync point to verify full compaction path is taken
|
||||
bool trivial_move_executed = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::CompactFilesImpl:TrivialMove",
|
||||
[&](void* /*arg*/) { trivial_move_executed = true; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Try to move from L1 to L6 - this should NOT be a trivial move
|
||||
// because the file overlaps with the existing file in L6
|
||||
ASSERT_OK(db->CompactFiles(compact_option, {l1_file}, 6));
|
||||
|
||||
// Verify trivial move was NOT executed (full compaction happened)
|
||||
ASSERT_FALSE(trivial_move_executed);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// Verify the result - should have merged data in L6
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
ASSERT_EQ(meta.levels[1].files.size(), 0); // L1 should be empty
|
||||
// L6 should have the merged file (may be 1 file if merged, or 2 if not)
|
||||
ASSERT_GE(meta.levels[6].files.size(), 1);
|
||||
|
||||
// Verify updated values are present
|
||||
for (int i = 50; i <= 60; i++) {
|
||||
std::string key = "m" + std::to_string(i);
|
||||
std::string value;
|
||||
ASSERT_OK(db->Get(ReadOptions(), key, &value));
|
||||
ASSERT_EQ(value, "updated_value_" + key);
|
||||
}
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -281,8 +281,9 @@ 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,
|
||||
uint32_t _max_subcompactions, std::vector<FileMetaData*> _grandparents,
|
||||
CompressionOptions _compression_opts,
|
||||
Temperature _output_temperature_override, 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,
|
||||
@@ -303,7 +304,7 @@ Compaction::Compaction(
|
||||
output_path_id_(_output_path_id),
|
||||
output_compression_(_compression),
|
||||
output_compression_opts_(_compression_opts),
|
||||
output_temperature_(_output_temperature),
|
||||
output_temperature_override_(_output_temperature_override),
|
||||
deletion_compaction_(_compaction_reason == CompactionReason::kFIFOTtl ||
|
||||
_compaction_reason ==
|
||||
CompactionReason::kFIFOMaxSize),
|
||||
@@ -1128,4 +1129,17 @@ 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,7 +90,8 @@ 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, uint32_t max_subcompactions,
|
||||
Temperature output_temperature_override,
|
||||
uint32_t max_subcompactions,
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
std::optional<SequenceNumber> earliest_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
@@ -409,7 +410,11 @@ class Compaction {
|
||||
|
||||
uint64_t max_compaction_bytes() const { return max_compaction_bytes_; }
|
||||
|
||||
Temperature output_temperature() const { return output_temperature_; }
|
||||
// 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;
|
||||
|
||||
uint32_t max_subcompactions() const { return max_subcompactions_; }
|
||||
|
||||
@@ -541,7 +546,7 @@ class Compaction {
|
||||
const uint32_t output_path_id_;
|
||||
CompressionType output_compression_;
|
||||
CompressionOptions output_compression_opts_;
|
||||
Temperature output_temperature_;
|
||||
Temperature output_temperature_override_;
|
||||
// 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,17 +1120,15 @@ void CompactionIterator::NextFromInput() {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -183,10 +183,20 @@ class CompactionIterator {
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
// @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.
|
||||
// @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()`.
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
@@ -255,7 +265,21 @@ 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_; }
|
||||
|
||||
+892
-282
File diff suppressed because it is too large.
Load diff
+182
-12
@@ -176,9 +176,20 @@ 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);
|
||||
known_single_subcompact,
|
||||
const CompactionProgress& compaction_progress = CompactionProgress{},
|
||||
log::Writer* compaction_progress_writer = nullptr);
|
||||
|
||||
// REQUIRED mutex not held
|
||||
// Launch threads for each subcompaction and wait for them to finish. After
|
||||
@@ -196,7 +207,8 @@ class CompactionJob {
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
protected:
|
||||
void UpdateCompactionJobOutputStats(
|
||||
void UpdateCompactionJobOutputStatsFromInternalStats(
|
||||
const Status& status,
|
||||
const InternalStats::CompactionStatsFull& internal_stats) const;
|
||||
|
||||
void LogCompaction();
|
||||
@@ -242,9 +254,10 @@ class CompactionJob {
|
||||
// num_input_range_del are calculated successfully.
|
||||
//
|
||||
// This should be called only once for compactions (not per subcompaction)
|
||||
bool BuildStatsFromInputFiles(uint64_t* num_input_range_del = nullptr);
|
||||
bool UpdateInternalStatsFromInputFiles(
|
||||
uint64_t* num_input_range_del = nullptr);
|
||||
|
||||
void UpdateCompactionJobInputStats(
|
||||
void UpdateCompactionJobInputStatsFromInternalStats(
|
||||
const InternalStats::CompactionStatsFull& internal_stats,
|
||||
uint64_t num_input_range_del) const;
|
||||
|
||||
@@ -258,6 +271,10 @@ 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();
|
||||
@@ -287,7 +304,10 @@ class CompactionJob {
|
||||
Status SyncOutputDirectories();
|
||||
Status VerifyOutputFiles();
|
||||
void SetOutputTableProperties();
|
||||
void AggregateSubcompactionStats();
|
||||
// Aggregates subcompaction output stats to internal stat, and aggregates
|
||||
// subcompaction's compaction job stats to the whole entire surrounding
|
||||
// compaction job stats.
|
||||
void AggregateSubcompactionOutputAndJobStats();
|
||||
Status VerifyCompactionRecordCounts(bool stats_built_from_input_table_prop,
|
||||
uint64_t num_input_range_del);
|
||||
void FinalizeCompactionRun(const Status& status,
|
||||
@@ -297,15 +317,123 @@ 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,
|
||||
SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs,
|
||||
const Slice& next_table_min_key,
|
||||
const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key);
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status,
|
||||
const ParsedInternalKey& prev_table_last_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 InstallCompactionResults(bool* compaction_released);
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs);
|
||||
@@ -382,6 +510,9 @@ 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);
|
||||
@@ -389,6 +520,43 @@ 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_table_last_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
|
||||
@@ -538,7 +706,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
|
||||
// REQUIRED: mutex held
|
||||
// Like CompactionJob::Prepare()
|
||||
void Prepare();
|
||||
void Prepare(
|
||||
const CompactionProgress& compaction_progress = CompactionProgress{},
|
||||
log::Writer* compaction_progress_writer = nullptr);
|
||||
|
||||
// Run the compaction in current thread and return the result
|
||||
Status Run();
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#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"
|
||||
@@ -210,12 +211,12 @@ class CompactionJobTestBase : public testing::Test {
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
versions_(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false)),
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
error_handler_(nullptr, db_options_, &mutex_),
|
||||
@@ -545,13 +546,13 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ASSERT_OK(s);
|
||||
db_options_.info_log = info_log;
|
||||
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
compaction_job_stats_.Reset();
|
||||
|
||||
VersionEdit new_db;
|
||||
@@ -2409,6 +2410,450 @@ 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};
|
||||
|
||||
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_.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 exists_progress,
|
||||
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 (exists_progress) {
|
||||
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 /* exists_progress */, true /* cancelled_past_mid_point*/);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnSameKey) {
|
||||
NewDB();
|
||||
|
||||
RunCancelAndResumeTest(
|
||||
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
|
||||
"val1"}} /* input_file_1 */,
|
||||
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeValue),
|
||||
"val2"}} /* input_file_2 */,
|
||||
2U /* last_sequence */, {1U} /* snapshots */,
|
||||
"" /* expected_next_key_to_compact */,
|
||||
{kCancelBeforeThisKey, kCancelBeforeThisKey} /* expected_input_keys */,
|
||||
false /* exists_progress */);
|
||||
}
|
||||
|
||||
TEST_F(ResumableCompactionJobTest, NoProgressResumeOnDeleteRange) {
|
||||
NewDB();
|
||||
|
||||
RunCancelAndResumeTest(
|
||||
{{KeyStr(kCancelBeforeThisKey, 1U, kTypeValue),
|
||||
"val1"}} /* input_file_1 */,
|
||||
{{KeyStr(kCancelBeforeThisKey, 2U, kTypeRangeDeletion),
|
||||
"val2"}} /* input_file_2 */,
|
||||
2U /* last_sequence */, {1U} /* snapshots */,
|
||||
"" /* expected_next_key_to_compact */,
|
||||
{kCancelBeforeThisKey, kCancelBeforeThisKey} /* expected_input_keys */,
|
||||
false /* exists_progress */);
|
||||
}
|
||||
|
||||
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 */,
|
||||
true /* exists_progress */);
|
||||
}
|
||||
|
||||
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 */,
|
||||
true /* exists_progress */);
|
||||
}
|
||||
|
||||
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 */,
|
||||
true /* exists_progress */);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -56,6 +56,7 @@ 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;
|
||||
}
|
||||
@@ -358,7 +359,8 @@ 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 CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_table_last_internal_key) {
|
||||
Status s;
|
||||
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
|
||||
if (is_range_del && compaction_->bottommost_level()) {
|
||||
@@ -369,7 +371,8 @@ Status CompactionOutputs::AddToOutput(
|
||||
}
|
||||
const Slice& key = c_iter.key();
|
||||
if (ShouldStopBefore(c_iter) && HasBuilder()) {
|
||||
s = close_file_func(*this, c_iter.InputStatus(), key);
|
||||
s = close_file_func(c_iter.InputStatus(), prev_table_last_internal_key, key,
|
||||
&c_iter, *this);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class CompactionOutputs;
|
||||
using CompactionFileOpenFunc = std::function<Status(CompactionOutputs&)>;
|
||||
using CompactionFileCloseFunc =
|
||||
std::function<Status(CompactionOutputs&, const Status&, const Slice&)>;
|
||||
std::function<Status(const Status&, const ParsedInternalKey&, const Slice&,
|
||||
const CompactionIterator*, CompactionOutputs&)>;
|
||||
|
||||
// Files produced by subcompaction, most of the functions are used by
|
||||
// compaction_job Open/Close compaction file functions.
|
||||
@@ -58,6 +59,8 @@ 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);
|
||||
|
||||
@@ -168,6 +171,10 @@ 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;
|
||||
@@ -191,6 +198,10 @@ 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;
|
||||
|
||||
@@ -250,7 +261,8 @@ 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 CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_table_last_internal_key);
|
||||
|
||||
// Close the current output. `open_file_func` is needed for creating new file
|
||||
// for range-dels only output file.
|
||||
@@ -266,9 +278,12 @@ 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(*this, status, empty_key);
|
||||
Status s = close_file_func(status, empty_internal_key, empty_key,
|
||||
nullptr /* c_iter */, *this);
|
||||
if (!s.ok() && status.ok()) {
|
||||
status = s;
|
||||
}
|
||||
@@ -296,6 +311,9 @@ 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_;
|
||||
|
||||
|
||||
@@ -375,12 +375,13 @@ 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),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_options.output_temperature_override,
|
||||
compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, earliest_snapshot, snapshot_checker,
|
||||
CompactionReason::kManualCompaction);
|
||||
@@ -679,8 +680,7 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
compact_range_options.target_path_id,
|
||||
GetCompressionType(vstorage, mutable_cf_options, output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_range_options.max_subcompactions,
|
||||
Temperature::kUnknown, compact_range_options.max_subcompactions,
|
||||
/* grandparents */ {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kManualCompaction,
|
||||
trim_ts, /* score */ -1,
|
||||
@@ -871,8 +871,8 @@ Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
GetCompressionType(vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
Temperature::kUnknown, 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,
|
||||
|
||||
@@ -124,8 +124,7 @@ 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,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kFIFOTtl,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
@@ -194,8 +193,7 @@ 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,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
0 /* max_subcompactions */, {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
@@ -258,6 +256,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
// better serves a major type of FIFO use cases where smaller keys are
|
||||
// associated with older data.
|
||||
for (const auto& f : last_level_files) {
|
||||
if (f->being_compacted) {
|
||||
continue;
|
||||
}
|
||||
total_size -= f->fd.file_size;
|
||||
inputs[0].files.push_back(f);
|
||||
char tmp_fsize[16];
|
||||
@@ -291,8 +292,7 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
/* target_file_size */ 0,
|
||||
/* max_compaction_bytes */ 0,
|
||||
/* output_path_id */ 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kFIFOMaxSize,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
@@ -387,12 +387,14 @@ 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 " for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(), est_newest_key_time,
|
||||
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
|
||||
" 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());
|
||||
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_),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
Temperature::kUnknown,
|
||||
/* 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->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), 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->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), 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->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), 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->output_temperature(), Temperature::kCold);
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), Temperature::kCold);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
@@ -1464,12 +1464,35 @@ 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->output_temperature(), Temperature::kWarm);
|
||||
ASSERT_EQ(compaction->GetOutputTemperature(), 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;
|
||||
|
||||
@@ -1093,7 +1093,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
output_level, 1, enable_compression),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_,
|
||||
output_level, enable_compression),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
Temperature::kUnknown,
|
||||
/* 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 */),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
@@ -1594,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),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, grandparents, earliest_snapshot_,
|
||||
snapshot_checker_, CompactionReason::kFilesMarkedForCompaction,
|
||||
/* trim_ts */ "", score_,
|
||||
@@ -1690,7 +1690,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
true /* enable_compression */),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {},
|
||||
/* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, compaction_reason,
|
||||
|
||||
@@ -326,7 +326,9 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
compaction_input_(compaction_service_input),
|
||||
compaction_result_(compaction_service_result) {}
|
||||
|
||||
void CompactionServiceCompactionJob::Prepare() {
|
||||
void CompactionServiceCompactionJob::Prepare(
|
||||
const CompactionProgress& compaction_progress,
|
||||
log::Writer* compaction_progress_writer) {
|
||||
std::optional<Slice> begin;
|
||||
if (compaction_input_.has_begin) {
|
||||
begin = compaction_input_.begin;
|
||||
@@ -335,7 +337,8 @@ void CompactionServiceCompactionJob::Prepare() {
|
||||
if (compaction_input_.has_end) {
|
||||
end = compaction_input_.end;
|
||||
}
|
||||
CompactionJob::Prepare(std::make_pair(begin, end));
|
||||
CompactionJob::Prepare(std::make_pair(begin, end), compaction_progress,
|
||||
compaction_progress_writer);
|
||||
}
|
||||
|
||||
Status CompactionServiceCompactionJob::Run() {
|
||||
@@ -408,7 +411,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
|
||||
UpdateCompactionJobOutputStats(internal_stats_);
|
||||
UpdateCompactionJobOutputStatsFromInternalStats(status, 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,6 +4,7 @@
|
||||
// (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"
|
||||
@@ -16,17 +17,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)),
|
||||
options_(options),
|
||||
statistics_(statistics),
|
||||
options_(options),
|
||||
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_(listeners),
|
||||
listeners_(std::move(listeners)),
|
||||
table_properties_collector_factories_(
|
||||
std::move(table_properties_collector_factories)) {}
|
||||
|
||||
@@ -72,6 +73,31 @@ 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 =
|
||||
@@ -94,26 +120,7 @@ class MyTestCompactionService : public CompactionService {
|
||||
options_override.table_properties_collector_factories =
|
||||
table_properties_collector_factories_;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return options_override;
|
||||
}
|
||||
|
||||
void CancelAwaitingJobs() override { canceled_ = true; }
|
||||
@@ -160,14 +167,21 @@ class MyTestCompactionService : public CompactionService {
|
||||
return final_updated_status_.load();
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic_int compaction_num_{0};
|
||||
const std::string db_path_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
std::map<std::string, std::string> jobs_;
|
||||
std::map<std::string, CompactionServiceJobInfo> infos_;
|
||||
const std::string db_path_;
|
||||
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};
|
||||
Options options_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
CompactionServiceJobInfo start_info_;
|
||||
CompactionServiceJobInfo wait_info_;
|
||||
bool is_override_start_status_ = false;
|
||||
@@ -177,7 +191,6 @@ 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>>
|
||||
@@ -463,8 +476,6 @@ 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(
|
||||
@@ -472,83 +483,97 @@ TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
|
||||
[&](void* arg) {
|
||||
num_files_after_filtered = *static_cast<size_t*>(arg);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
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));
|
||||
for (auto compaction_style : {CompactionStyle::kCompactionStyleLevel,
|
||||
CompactionStyle::kCompactionStyleUniversal}) {
|
||||
SCOPED_TRACE("Style: " + std::to_string(compaction_style));
|
||||
options.compaction_style = compaction_style;
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
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));
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1022,6 +1047,7 @@ TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
|
||||
Destroy(options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.paranoid_file_checks = paranoid_file_check_enabled;
|
||||
options.verify_output_flags = VerifyOutputFlags::kVerifyNone;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
@@ -1076,6 +1102,87 @@ TEST_F(CompactionServiceTest, CorruptedOutputParanoidFileCheck) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutputVerifyOutputFlags) {
|
||||
for (VerifyOutputFlags verify_output_flags :
|
||||
{VerifyOutputFlags::kVerifyNone,
|
||||
VerifyOutputFlags::kEnableForLocalCompaction |
|
||||
VerifyOutputFlags::kVerifyBlockChecksum,
|
||||
VerifyOutputFlags::kEnableForRemoteCompaction |
|
||||
VerifyOutputFlags::kVerifyBlockChecksum,
|
||||
VerifyOutputFlags::kEnableForRemoteCompaction |
|
||||
VerifyOutputFlags::kVerifyIteration,
|
||||
VerifyOutputFlags::kVerifyAll}) {
|
||||
SCOPED_TRACE(
|
||||
"verify_output_flags=" +
|
||||
std::to_string(static_cast<std::underlying_type_t<VerifyOutputFlags>>(
|
||||
verify_output_flags)));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
Destroy(options);
|
||||
options.disable_auto_compactions = true;
|
||||
options.paranoid_file_checks = false;
|
||||
options.verify_output_flags = verify_output_flags;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
*(static_cast<CompactionServiceResult**>(arg));
|
||||
ASSERT_TRUE(compaction_result != nullptr &&
|
||||
!compaction_result->output_files.empty());
|
||||
// Corrupt files here
|
||||
for (const auto& output_file : compaction_result->output_files) {
|
||||
std::string file_name =
|
||||
compaction_result->output_path + "/" + output_file.file_name;
|
||||
|
||||
// Corrupt very small range of bytes. This corruption is so small
|
||||
// that this isn't caught by default light-weight check
|
||||
ASSERT_OK(test::CorruptFile(env_, file_name, 0, 1,
|
||||
false /* verifyChecksum */));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
const bool is_enabled_for_remote_compaction =
|
||||
!!(verify_output_flags & VerifyOutputFlags::kEnableForRemoteCompaction);
|
||||
const bool should_verify_block_checksum =
|
||||
!!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum);
|
||||
const bool should_verify_iteration =
|
||||
!!(verify_output_flags & VerifyOutputFlags::kVerifyIteration);
|
||||
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
if (is_enabled_for_remote_compaction &&
|
||||
(should_verify_block_checksum || should_verify_iteration)) {
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
} else {
|
||||
// CompactRange() goes through if block checksum wasn't verified
|
||||
ASSERT_OK(s);
|
||||
}
|
||||
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// On the worker side, the compaction is considered success
|
||||
// Verification is done on the primary side
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, TruncatedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -1993,6 +2100,338 @@ 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,11 +108,13 @@ 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 CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_table_last_internal_key) {
|
||||
// update target output
|
||||
current_outputs_ =
|
||||
use_proximal_output ? &proximal_level_outputs_ : &compaction_outputs_;
|
||||
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func);
|
||||
return current_outputs_->AddToOutput(iter, open_file_func, close_file_func,
|
||||
prev_table_last_internal_key);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -191,6 +191,14 @@ 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();
|
||||
}
|
||||
@@ -200,10 +208,20 @@ 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 CompactionFileCloseFunc& close_file_func,
|
||||
const ParsedInternalKey& prev_table_last_internal_key);
|
||||
|
||||
// Close all compaction output files, both output_to_proximal_level outputs
|
||||
// and normal outputs.
|
||||
@@ -233,6 +251,8 @@ 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,7 +1764,10 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
options.env = mock_env_.get();
|
||||
options.level0_file_num_compaction_trigger = kNumTrigger;
|
||||
options.num_levels = kNumLevels;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
// 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();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -1791,6 +1794,10 @@ 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
|
||||
@@ -1799,8 +1806,14 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
for (auto t : kKnownTemperatures) {
|
||||
if (t == options.last_level_temperature) {
|
||||
ASSERT_GT(GetSstSizeHelper(t), 0);
|
||||
} else {
|
||||
ASSERT_EQ(GetSstSizeHelper(t), 0);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
+1
-24
@@ -675,30 +675,6 @@ TEST_F(DBBasicTest, Flush) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ManifestRollOver) {
|
||||
do {
|
||||
Options options;
|
||||
options.max_manifest_file_size = 10; // 10 bytes
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
{
|
||||
ASSERT_OK(Put(1, "manifest_key1", std::string(1000, '1')));
|
||||
ASSERT_OK(Put(1, "manifest_key2", std::string(1000, '2')));
|
||||
ASSERT_OK(Put(1, "manifest_key3", std::string(1000, '3')));
|
||||
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
|
||||
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_GT(manifest_after_flush, manifest_before_flush);
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
|
||||
// check if a new manifest file got inserted or not.
|
||||
ASSERT_EQ(std::string(1000, '1'), Get(1, "manifest_key1"));
|
||||
ASSERT_EQ(std::string(1000, '2'), Get(1, "manifest_key2"));
|
||||
ASSERT_EQ(std::string(1000, '3'), Get(1, "manifest_key3"));
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, IdentityAcrossRestarts) {
|
||||
constexpr size_t kMinIdSize = 10;
|
||||
do {
|
||||
@@ -5087,6 +5063,7 @@ 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
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#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"
|
||||
@@ -7128,6 +7129,70 @@ TEST_F(DBCompactionTest, PartialManualCompaction) {
|
||||
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ConcurrentFIFOPickingSameFileBug) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.compaction_style = CompactionStyle::kCompactionStyleLevel;
|
||||
opts.num_levels = 3;
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.max_background_jobs = 3;
|
||||
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Create a non-L0 SST file for multi-level FIFO size-based compaction later
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
Options opts_new(opts);
|
||||
opts_new.compaction_style = CompactionStyle::kCompactionStyleFIFO;
|
||||
opts_new.max_open_files = -1;
|
||||
// Set a low threshold to trigger multi-level size-based compaction
|
||||
opts_new.compaction_options_fifo.max_table_files_size = 1;
|
||||
|
||||
Reopen(opts_new);
|
||||
|
||||
const CompactRangeOptions cro;
|
||||
const Slice begin_key("k1");
|
||||
const Slice end_key("k2");
|
||||
|
||||
std::unique_ptr<port::Thread> concurrent_compaction;
|
||||
|
||||
bool within_first_compaction = true;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifestStart", [&](void* /*arg*/) {
|
||||
if (!within_first_compaction) {
|
||||
return;
|
||||
}
|
||||
within_first_compaction = false;
|
||||
|
||||
// To allow the second/concurrent compaction to still see the non-L0
|
||||
// SST file and coerce the bug of picking that file
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::BackgroundCompaction:BeforeCompaction",
|
||||
"VersionSet::LogAndApply:WriteManifest"},
|
||||
});
|
||||
|
||||
concurrent_compaction.reset(new port::Thread([&]() {
|
||||
// Before the fix, the second CompactRange() will either fail the
|
||||
// assertion of double file picking `being_compacted !=
|
||||
// inputs_[i][j]->being_compacted` in debug mode or cause LSM shape
|
||||
// corruption "Cannot delete table file XXX from level 2 since it is
|
||||
// not in the LSM tree" in release mode
|
||||
Status s = db_->CompactRange(cro, &begin_key, &end_key);
|
||||
ASSERT_OK(s);
|
||||
}));
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = db_->CompactRange(cro, &begin_key, &end_key);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_OK(s);
|
||||
|
||||
concurrent_compaction->join();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ManualCompactionFailsInReadOnlyMode) {
|
||||
// Regression test for bug where manual compaction hangs forever when the DB
|
||||
// is in read-only mode. Verify it now at least returns, despite failing.
|
||||
@@ -9727,6 +9792,7 @@ 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) {
|
||||
@@ -9737,6 +9803,8 @@ 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++;
|
||||
@@ -9810,6 +9878,261 @@ 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;
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBEtc3Test : public DBTestBase {
|
||||
public:
|
||||
DBEtc3Test() : DBTestBase("db_etc3_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
TEST_F(DBEtc3Test, ManifestRollOver) {
|
||||
do {
|
||||
Options options;
|
||||
// Force new manifest on each manifest write
|
||||
options.max_manifest_file_size = 0;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
{
|
||||
ASSERT_OK(Put(1, "key1", std::string(1000, '1')));
|
||||
ASSERT_OK(Put(1, "key2", std::string(1000, '2')));
|
||||
ASSERT_OK(Put(1, "key3", std::string(1000, '3')));
|
||||
uint64_t manifest_before_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_OK(Flush(1)); // This should trigger LogAndApply.
|
||||
uint64_t manifest_after_flush = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
ASSERT_GT(manifest_after_flush, manifest_before_flush);
|
||||
// Re-open should always re-create manifest file
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_GT(dbfull()->TEST_Current_Manifest_FileNo(), manifest_after_flush);
|
||||
ASSERT_EQ(std::string(1000, '1'), Get(1, "key1"));
|
||||
ASSERT_EQ(std::string(1000, '2'), Get(1, "key2"));
|
||||
ASSERT_EQ(std::string(1000, '3'), Get(1, "key3"));
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBEtc3Test, AutoTuneManifestSize) {
|
||||
// Ensure we have auto-tuning beyond max_manifest_file_size by default
|
||||
ASSERT_EQ(DBOptions{}.max_manifest_space_amp_pct, 500);
|
||||
|
||||
Options options = CurrentOptions();
|
||||
ASSERT_OK(db_->SetOptions({{"level0_file_num_compaction_trigger", "20"}}));
|
||||
|
||||
// Use large column family names to essentially control the amount of payload
|
||||
// data needed for the manifest file. Drop manifest entries don't include the
|
||||
// CF name so are small.
|
||||
uint64_t prev_manifest_num = 0, cur_manifest_num = 0;
|
||||
std::deque<ColumnFamilyHandle*> handles;
|
||||
int counter = 5;
|
||||
auto AddCfFn = [&]() {
|
||||
std::string name = "cf" + std::to_string(counter++);
|
||||
name.resize(1000, 'a');
|
||||
ASSERT_OK(db_->CreateColumnFamily(options, name, &handles.emplace_back()));
|
||||
prev_manifest_num = cur_manifest_num;
|
||||
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
};
|
||||
auto DropCfFn = [&]() {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles.front()));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(handles.front()));
|
||||
handles.pop_front();
|
||||
prev_manifest_num = cur_manifest_num;
|
||||
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
};
|
||||
auto TrivialManifestWriteFn = [&]() {
|
||||
ASSERT_OK(Put("x", std::to_string(counter++)));
|
||||
ASSERT_OK(Flush());
|
||||
prev_manifest_num = cur_manifest_num;
|
||||
cur_manifest_num = dbfull()->TEST_Current_Manifest_FileNo();
|
||||
};
|
||||
|
||||
options.max_manifest_file_size = 1000000;
|
||||
options.max_manifest_space_amp_pct = 0; // no auto-tuning yet
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// With the generous (minimum) maximum manifest size, should not be rotated
|
||||
AddCfFn();
|
||||
AddCfFn();
|
||||
AddCfFn();
|
||||
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// Change options for small max and (still) no auto-tuning
|
||||
ASSERT_OK(db_->SetDBOptions({{"max_manifest_file_size", "3000"}}));
|
||||
|
||||
// Takes effect on the next manifest write
|
||||
TrivialManifestWriteFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// Now we have to rewrite the whole manifest on each write because the
|
||||
// compacted size exceeds the "max" size.
|
||||
AddCfFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
DropCfFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
AddCfFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
TrivialManifestWriteFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// Enabling auto-tuning should fix this, immediately for next manifest writes.
|
||||
// This will allow up to double-ish the size of the compacted manifest,
|
||||
// which last should have been 4000 + some bytes.
|
||||
ASSERT_EQ(handles.size(), 4U);
|
||||
ASSERT_OK(db_->SetDBOptions({{"max_manifest_space_amp_pct", "105"}}));
|
||||
|
||||
// After 9 CF names should be enough to rotate the manifest
|
||||
for (int i = 1; i <= 5; ++i) {
|
||||
if ((i % 2) == 1) {
|
||||
DropCfFn();
|
||||
}
|
||||
AddCfFn();
|
||||
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
|
||||
}
|
||||
TrivialManifestWriteFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// We now have a different last compacted manifest size, should be
|
||||
// able to go beyond 9 CFs named in manifest this time.
|
||||
ASSERT_EQ(handles.size(), 6U);
|
||||
|
||||
DropCfFn();
|
||||
DropCfFn();
|
||||
for (int i = 1; i <= 4; ++i) {
|
||||
DropCfFn();
|
||||
AddCfFn();
|
||||
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
|
||||
}
|
||||
// We've written 10 named CFs to the manifest. We should be able to
|
||||
// dynamically change the auto-tuning still based on the last "compacted"
|
||||
// manifest size of 7000 + some bytes.
|
||||
ASSERT_OK(db_->SetDBOptions({{"max_manifest_space_amp_pct", "51"}}));
|
||||
TrivialManifestWriteFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
// And the "compacted" manifest size has reset again, so should be changed
|
||||
// again sooner.
|
||||
ASSERT_EQ(handles.size(), 4U);
|
||||
for (int i = 1; i <= 2; ++i) {
|
||||
AddCfFn();
|
||||
ASSERT_EQ(prev_manifest_num, cur_manifest_num);
|
||||
}
|
||||
// Enough for manifest change
|
||||
AddCfFn();
|
||||
ASSERT_LT(prev_manifest_num, cur_manifest_num);
|
||||
|
||||
// Wrap up
|
||||
while (!handles.empty()) {
|
||||
DropCfFn();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -3561,6 +3561,152 @@ 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
|
||||
// donesn't install them yet), followed by deleting those 4 and adding a new
|
||||
// doesn'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 teh Barrier* functions to ensure that the follower first sees the 4
|
||||
// We use the 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
|
||||
|
||||
+25
-8
@@ -258,10 +258,10 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
[this]() { this->TriggerPeriodicCompaction(); });
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &immutable_db_options_, file_options_, table_cache_.get(),
|
||||
write_buffer_manager_, &write_controller_, &block_cache_tracer_,
|
||||
io_tracer_, db_id_, db_session_id_, options.daily_offpeak_time_utc,
|
||||
&error_handler_, read_only));
|
||||
dbname_, &immutable_db_options_, mutable_db_options_, file_options_,
|
||||
table_cache_.get(), write_buffer_manager_, &write_controller_,
|
||||
&block_cache_tracer_, io_tracer_, db_id_, db_session_id_,
|
||||
options.daily_offpeak_time_utc, &error_handler_, read_only));
|
||||
column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(versions_->GetColumnFamilySet()));
|
||||
|
||||
@@ -1069,7 +1069,7 @@ void DBImpl::DumpStats() {
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (!cfd->initialized()) {
|
||||
if (!cfd->initialized() || cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1245,13 +1245,11 @@ 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();
|
||||
@@ -1414,7 +1412,7 @@ Status DBImpl::SetDBOptions(
|
||||
file_options_for_compaction_ = FileOptions(new_db_options);
|
||||
file_options_for_compaction_ = fs_->OptimizeForCompactionTableWrite(
|
||||
file_options_for_compaction_, immutable_db_options_);
|
||||
versions_->ChangeFileOptions(mutable_db_options_);
|
||||
versions_->UpdatedMutableDbOptions(mutable_db_options_, &mutex_);
|
||||
// TODO(xiez): clarify why apply optimize for read to write options
|
||||
file_options_for_compaction_ = fs_->OptimizeForCompactionTableRead(
|
||||
file_options_for_compaction_, immutable_db_options_);
|
||||
@@ -1482,6 +1480,12 @@ 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;
|
||||
@@ -5043,6 +5047,19 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* metadata) {
|
||||
assert(column_family);
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
cfd->current()->GetColumnFamilyMetaData(options, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::GetAllColumnFamilyMetaData(
|
||||
std::vector<ColumnFamilyMetaData>* metadata) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
+18
-2
@@ -484,10 +484,13 @@ class DBImpl : public DB {
|
||||
const FlushOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families) override;
|
||||
Status FlushWAL(bool sync) override {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
return FlushWAL(WriteOptions(), sync);
|
||||
FlushWALOptions options;
|
||||
options.sync = sync;
|
||||
return FlushWAL(options);
|
||||
}
|
||||
|
||||
Status FlushWAL(const FlushWALOptions& options) override;
|
||||
|
||||
virtual Status FlushWAL(const WriteOptions& write_options, bool sync);
|
||||
bool WALBufferIsEmpty();
|
||||
Status SyncWAL() override;
|
||||
@@ -570,6 +573,11 @@ class DBImpl : public DB {
|
||||
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
|
||||
// Get column family metadata with filtering based on key range and level
|
||||
void GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* metadata) override;
|
||||
|
||||
void GetAllColumnFamilyMetaData(
|
||||
std::vector<ColumnFamilyMetaData>* metadata) override;
|
||||
|
||||
@@ -2393,6 +2401,14 @@ class DBImpl : public DB {
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
CompactionJobInfo* compaction_job_info);
|
||||
|
||||
// Helper function to perform trivial move by updating manifest metadata
|
||||
// without rewriting data files. This is called when IsTrivialMove() is true.
|
||||
// REQUIRES: mutex held
|
||||
// Returns: Status of the trivial move operation
|
||||
Status PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
|
||||
bool& compaction_released, size_t& moved_files,
|
||||
size_t& moved_bytes);
|
||||
|
||||
// REQUIRES: mutex unlocked
|
||||
void TrackOrUntrackFiles(const std::vector<std::string>& existing_data_files,
|
||||
bool track);
|
||||
|
||||
@@ -1424,6 +1424,56 @@ Status DBImpl::CompactFiles(const CompactionOptions& compact_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::PerformTrivialMove(Compaction& c, LogBuffer* log_buffer,
|
||||
bool& compaction_released,
|
||||
size_t& moved_files, size_t& moved_bytes) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Moving %d files to level-%d\n",
|
||||
c.column_family_data()->GetName().c_str(),
|
||||
static_cast<int>(c.num_input_files(0)), c.output_level());
|
||||
|
||||
// Move files to the output level by editing the manifest
|
||||
for (unsigned int l = 0; l < c.num_input_levels(); l++) {
|
||||
if (c.level(l) == c.output_level()) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < c.num_input_files(l); i++) {
|
||||
FileMetaData* f = c.input(l, i);
|
||||
c.edit()->DeleteFile(c.level(l), f->fd.GetNumber());
|
||||
c.edit()->AddFile(c.output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest,
|
||||
f->fd.smallest_seqno, f->fd.largest_seqno,
|
||||
f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->epoch_number,
|
||||
f->file_checksum, f->file_checksum_func_name,
|
||||
f->unique_id, f->compensated_range_deletion_size,
|
||||
f->tail_size, f->user_defined_timestamps_persisted);
|
||||
moved_bytes += static_cast<size_t>(c.input(l, i)->fd.GetFileSize());
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer, "[%s] Moved #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
|
||||
c.column_family_data()->GetName().c_str(), f->fd.GetNumber(),
|
||||
c.output_level(), f->fd.GetFileSize());
|
||||
}
|
||||
moved_files += c.num_input_files(l);
|
||||
}
|
||||
|
||||
// Install the new version
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
const WriteOptions write_options(Env::IOActivity::kCompaction);
|
||||
Status status = versions_->LogAndApply(
|
||||
c.column_family_data(), read_options, write_options, c.edit(), &mutex_,
|
||||
directories_.GetDbDir(), /*new_descriptor_log=*/false,
|
||||
/*column_family_options=*/nullptr,
|
||||
[&c, &compaction_released](const Status& s) {
|
||||
c.ReleaseCompactionFiles(s);
|
||||
compaction_released = true;
|
||||
});
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::CompactFilesImpl(
|
||||
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
|
||||
Version* version, const std::vector<std::string>& input_file_names,
|
||||
@@ -1511,6 +1561,63 @@ Status DBImpl::CompactFilesImpl(
|
||||
// deletion compaction currently not allowed in CompactFiles.
|
||||
assert(!c->deletion_compaction());
|
||||
|
||||
// Check if this can be a trivial move (metadata-only update)
|
||||
// Similar to the logic in DBImpl::BackgroundCompaction
|
||||
// Note: We disable trivial move when compaction_service is present because
|
||||
// the service expects all compactions to go through CompactionJob for
|
||||
// tracking
|
||||
bool is_trivial_move = compact_options.allow_trivial_move &&
|
||||
c->IsTrivialMove() &&
|
||||
immutable_db_options().compaction_service == nullptr;
|
||||
|
||||
if (is_trivial_move) {
|
||||
// Perform trivial move: just update manifest without rewriting data
|
||||
TEST_SYNC_POINT("DBImpl::CompactFilesImpl:TrivialMove");
|
||||
|
||||
bool compaction_released = false;
|
||||
size_t moved_files = 0;
|
||||
size_t moved_bytes = 0;
|
||||
Status status = PerformTrivialMove(
|
||||
*c.get(), log_buffer, compaction_released, moved_files, moved_bytes);
|
||||
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), job_context->superversion_contexts.data());
|
||||
|
||||
// Populate output file names for trivial move
|
||||
if (output_file_names != nullptr) {
|
||||
for (const auto& newf : c->edit()->GetNewFiles()) {
|
||||
output_file_names->push_back(TableFileName(
|
||||
c->immutable_options().cf_paths, newf.second.fd.GetNumber(),
|
||||
newf.second.fd.GetPathId()));
|
||||
}
|
||||
}
|
||||
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] Trivial move succeeded for %zu files, %zu bytes total\n",
|
||||
c->column_family_data()->GetName().c_str(), moved_files, moved_bytes);
|
||||
} else {
|
||||
if (!compaction_released) {
|
||||
c->ReleaseCompactionFiles(status);
|
||||
}
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Trivial move failed: %s\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
status.ToString().c_str());
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
|
||||
c.reset();
|
||||
bg_compaction_scheduled_--;
|
||||
if (bg_compaction_scheduled_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
// Not a trivial move, proceed with full compaction
|
||||
InitSnapshotContext(job_context);
|
||||
|
||||
std::unique_ptr<std::list<uint64_t>::iterator> pending_outputs_inserted_elem(
|
||||
@@ -1843,8 +1950,7 @@ 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,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
0 /* max_subcompactions, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
@@ -3861,7 +3967,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->output_temperature();
|
||||
copied_file_options.temperature = c->GetOutputTemperature();
|
||||
std::unique_ptr<WritableFileWriter> dest_writer;
|
||||
{
|
||||
std::unique_ptr<FSWritableFile> dest_file;
|
||||
@@ -3879,7 +3985,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->output_temperature()].c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
@@ -3901,7 +4007,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->output_temperature()].c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
c->mutable_cf_options()
|
||||
.compaction_options_fifo.trivial_copy_buffer_size);
|
||||
// Add IO_LOW HINT for compaction
|
||||
@@ -3941,7 +4047,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->output_temperature()].c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
@@ -3950,15 +4056,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->output_temperature()].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->GetOutputTemperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
break;
|
||||
}
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
@@ -3968,7 +4074,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->output_temperature()].c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
io_s.ToString().c_str());
|
||||
|
||||
FileMetaData out_file_metadata{
|
||||
@@ -3980,7 +4086,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
in_file->fd.smallest_seqno,
|
||||
in_file->fd.largest_seqno,
|
||||
false /* marked_for_compact */,
|
||||
c->output_temperature() /* temperature */,
|
||||
c->GetOutputTemperature() /* temperature */,
|
||||
in_file->oldest_blob_file_number,
|
||||
in_file->oldest_ancester_time,
|
||||
out_file_creation_time,
|
||||
@@ -4049,7 +4155,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->output_temperature()].c_str(),
|
||||
temperature_to_string[c->GetOutputTemperature()].c_str(),
|
||||
status.ToString().c_str(), io_s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
@@ -4075,35 +4181,6 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
NotifyOnCompactionBegin(c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
|
||||
// Move files to next level
|
||||
int32_t moved_files = 0;
|
||||
int64_t moved_bytes = 0;
|
||||
for (unsigned int l = 0; l < c->num_input_levels(); l++) {
|
||||
if (c->level(l) == c->output_level()) {
|
||||
continue;
|
||||
}
|
||||
for (size_t i = 0; i < c->num_input_files(l); i++) {
|
||||
FileMetaData* f = c->input(l, i);
|
||||
c->edit()->DeleteFile(c->level(l), f->fd.GetNumber());
|
||||
c->edit()->AddFile(
|
||||
c->output_level(), f->fd.GetNumber(), f->fd.GetPathId(),
|
||||
f->fd.GetFileSize(), f->smallest, f->largest, f->fd.smallest_seqno,
|
||||
f->fd.largest_seqno, f->marked_for_compaction, f->temperature,
|
||||
f->oldest_blob_file_number, f->oldest_ancester_time,
|
||||
f->file_creation_time, f->epoch_number, f->file_checksum,
|
||||
f->file_checksum_func_name, f->unique_id,
|
||||
f->compensated_range_deletion_size, f->tail_size,
|
||||
f->user_defined_timestamps_persisted);
|
||||
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] Moving #%" PRIu64 " to level-%d %" PRIu64 " bytes\n",
|
||||
c->column_family_data()->GetName().c_str(), f->fd.GetNumber(),
|
||||
c->output_level(), f->fd.GetFileSize());
|
||||
++moved_files;
|
||||
moved_bytes += f->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
if (c->compaction_reason() == CompactionReason::kLevelMaxLevelSize &&
|
||||
c->immutable_options().compaction_pri == kRoundRobin) {
|
||||
int start_level = c->start_level();
|
||||
@@ -4114,14 +4191,12 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
vstorage->GetNextCompactCursor(start_level, c->num_input_files(0)));
|
||||
}
|
||||
}
|
||||
status = versions_->LogAndApply(
|
||||
c->column_family_data(), read_options, write_options, c->edit(),
|
||||
&mutex_, directories_.GetDbDir(),
|
||||
/*new_descriptor_log=*/false, /*column_family_options=*/nullptr,
|
||||
[&c, &compaction_released](const Status& s) {
|
||||
c->ReleaseCompactionFiles(s);
|
||||
compaction_released = true;
|
||||
});
|
||||
|
||||
// Perform the trivial move
|
||||
size_t moved_files = 0;
|
||||
size_t moved_bytes = 0;
|
||||
status = PerformTrivialMove(*c.get(), log_buffer, compaction_released,
|
||||
moved_files, moved_bytes);
|
||||
io_s = versions_->io_status();
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), job_context->superversion_contexts.data());
|
||||
@@ -4136,8 +4211,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
<< "total_files_size" << moved_bytes;
|
||||
}
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] Moved #%d files to level-%d %" PRIu64 " bytes %s: %s\n",
|
||||
log_buffer, "[%s] Moved #%d files to level-%zu %zu bytes %s: %s\n",
|
||||
c->column_family_data()->GetName().c_str(), moved_files,
|
||||
c->output_level(), moved_bytes, status.ToString().c_str(),
|
||||
c->column_family_data()->current()->storage_info()->LevelSummary(&tmp));
|
||||
@@ -4428,7 +4502,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->output_temperature(), c->max_subcompactions(),
|
||||
c->GetOutputTemperature(), c->max_subcompactions(),
|
||||
c->grandparents(), std::nullopt /* earliest_snapshot */,
|
||||
nullptr /* snapshot_checker */, c->compaction_reason());
|
||||
|
||||
|
||||
@@ -614,6 +614,11 @@ 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->file_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
&impl->write_controller_, impl->io_tracer_));
|
||||
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
|
||||
impl->file_options_, impl->table_cache_.get(),
|
||||
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
|
||||
@@ -329,7 +329,7 @@ Status DBImpl::NewDB(std::vector<std::string>* new_filenames) {
|
||||
}
|
||||
FileTypeSet tmp_set = immutable_db_options_.checksum_handoff_file_types;
|
||||
file->SetPreallocationBlockSize(
|
||||
immutable_db_options_.manifest_preallocation_size);
|
||||
mutable_db_options_.manifest_preallocation_size);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), manifest, file_options, immutable_db_options_.clock,
|
||||
io_tracer_, nullptr /* stats */,
|
||||
|
||||
+668
-17
@@ -8,7 +8,12 @@
|
||||
#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"
|
||||
@@ -778,9 +783,9 @@ Status DB::OpenAsSecondary(
|
||||
handles->clear();
|
||||
DBImplSecondary* impl = new DBImplSecondary(tmp_opts, dbname, secondary_path);
|
||||
impl->versions_.reset(new ReactiveVersionSet(
|
||||
dbname, &impl->immutable_db_options_, impl->file_options_,
|
||||
impl->table_cache_.get(), impl->write_buffer_manager_,
|
||||
&impl->write_controller_, impl->io_tracer_));
|
||||
dbname, &impl->immutable_db_options_, impl->mutable_db_options_,
|
||||
impl->file_options_, impl->table_cache_.get(),
|
||||
impl->write_buffer_manager_, &impl->write_controller_, impl->io_tracer_));
|
||||
impl->column_family_memtables_.reset(
|
||||
new ColumnFamilyMemTablesImpl(impl->versions_->GetColumnFamilySet()));
|
||||
impl->wal_in_db_path_ = impl->immutable_db_options_.IsWalDirSameAsDBPath();
|
||||
@@ -823,18 +828,507 @@ 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;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Initialized compaction workspace with %zu subcompaction "
|
||||
"progress to resume",
|
||||
compaction_progress_.size());
|
||||
|
||||
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);
|
||||
}
|
||||
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;
|
||||
|
||||
// TODO(hx235): Resuming compaction is currently incompatible with
|
||||
// paranoid_file_checks=true because OutputValidator hash verification would
|
||||
// fail during compaction resumption. Before interruption, resuming
|
||||
// compaction needs to persist the hash of each output file to enable
|
||||
// validation after resumption. Alternatively and preferably, we could move
|
||||
// the output verification to happen immediately after each output file is
|
||||
// created. This workaround currently disables resuming compaction when
|
||||
// paranoid_file_checks is enabled. Note that paranoid_file_checks is
|
||||
// disabled by default.
|
||||
bool allow_resumption =
|
||||
options.allow_resumption &&
|
||||
!cfd->GetLatestMutableCFOptions().paranoid_file_checks;
|
||||
|
||||
if (options.allow_resumption &&
|
||||
cfd->GetLatestMutableCFOptions().paranoid_file_checks) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Resume compaction configured but disabled due to "
|
||||
"incompatible with paranoid_file_checks=true");
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -856,7 +1350,7 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
cfd->ioptions().level_compaction_dynamic_level_bytes);
|
||||
|
||||
std::vector<CompactionInputFiles> input_files;
|
||||
Status s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
|
||||
s = cfd->compaction_picker()->GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage, comp_options);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(
|
||||
@@ -879,23 +1373,22 @@ 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,
|
||||
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0,
|
||||
/*earliest_snapshot=*/job_context.snapshot_seqs.empty()
|
||||
? kMaxSequenceNumber
|
||||
: job_context.snapshot_seqs.front(),
|
||||
job_context.snapshot_checker));
|
||||
earliest_snapshot, 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());
|
||||
|
||||
@@ -913,13 +1406,15 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
options.canceled ? *options.canceled : kManualCompactionCanceledFalse_,
|
||||
input.db_id, db_session_id_, secondary_path_, input, result);
|
||||
|
||||
compaction_job.Prepare();
|
||||
compaction_job.Prepare(compaction_progress_,
|
||||
compaction_progress_writer.get());
|
||||
|
||||
mutex_.Unlock();
|
||||
s = compaction_job.Run();
|
||||
mutex_.Lock();
|
||||
|
||||
// clean up
|
||||
// These cleanup functions handle metadata and state cleanup only and
|
||||
// not the physical files
|
||||
compaction_job.io_status().PermitUncheckedError();
|
||||
compaction_job.CleanupCompaction();
|
||||
c->ReleaseCompactionFiles(s);
|
||||
@@ -927,6 +1422,18 @@ 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;
|
||||
}
|
||||
@@ -1082,4 +1589,148 @@ 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);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -303,6 +303,87 @@ 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_;
|
||||
@@ -311,6 +392,8 @@ 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,11 +1565,115 @@ 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;
|
||||
|
||||
+3
-10
@@ -240,16 +240,8 @@ class DBIter final : public Iterator {
|
||||
|
||||
bool PrepareValue() override;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
void Prepare(const MultiScanArgs& scan_opts) override;
|
||||
Status ValidateScanOptions(const MultiScanArgs& multiscan_opts) const;
|
||||
|
||||
private:
|
||||
DBIter(Env* _env, const ReadOptions& read_options,
|
||||
@@ -506,6 +498,7 @@ 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_;
|
||||
|
||||
+357
-66
@@ -4149,6 +4149,7 @@ 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());
|
||||
|
||||
@@ -4192,66 +4193,6 @@ 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) {
|
||||
@@ -4366,15 +4307,365 @@ TEST_P(DBMultiScanIteratorTest, RangeAcrossFiles) {
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
int i = 10;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_EQ(it.first.ToString(), Key(i));
|
||||
++i;
|
||||
try {
|
||||
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();
|
||||
}
|
||||
ASSERT_EQ(i, 90);
|
||||
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());
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -339,6 +339,135 @@ 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,12 +432,47 @@ TEST_F(DBOptionsTest, SetWalBytesPerSync) {
|
||||
ASSERT_GT(low_bytes_per_sync, counter);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, MutableManifestOptions) {
|
||||
// These aren't end-to-end tests, but sufficient to ensure the VersionSet
|
||||
// receives the updates with SetDBOptions
|
||||
for (int64_t i : {0, 1, 100, 100000, 10000000}) {
|
||||
ASSERT_OK(
|
||||
db_->SetDBOptions({{"max_manifest_file_size", std::to_string(i)}}));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(db_->GetDBOptions().max_manifest_file_size));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetMinMaxManifestFileSize()));
|
||||
if (i > 1) {
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(
|
||||
db_->SetDBOptions({{"max_manifest_space_amp_pct", std::to_string(i)}}));
|
||||
ASSERT_EQ(i, static_cast<int64_t>(
|
||||
db_->GetDBOptions().max_manifest_space_amp_pct));
|
||||
ASSERT_EQ(i,
|
||||
static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetMaxManifestSpaceAmpPct()));
|
||||
if (i > 1) {
|
||||
++i;
|
||||
}
|
||||
ASSERT_OK(db_->SetDBOptions(
|
||||
{{"manifest_preallocation_size", std::to_string(i)}}));
|
||||
ASSERT_EQ(i, static_cast<int64_t>(
|
||||
db_->GetDBOptions().manifest_preallocation_size));
|
||||
ASSERT_EQ(
|
||||
i, static_cast<int64_t>(
|
||||
dbfull()->GetVersionSet()->TEST_GetManifestPreallocationSize()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, WritableFileMaxBufferSize) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 1024 * 1024;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.env = env_;
|
||||
int buffer_size = 1024 * 1024;
|
||||
Reopen(options);
|
||||
|
||||
@@ -442,6 +442,107 @@ 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,6 +787,46 @@ 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"));
|
||||
|
||||
+245
@@ -1492,6 +1492,246 @@ TEST_F(DBTest, MetaDataTest) {
|
||||
CheckLiveFilesMeta(live_file_meta, files_by_level);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetColumnFamilyMetaDataWithKeyRangeAndLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
int64_t temp_time = 0;
|
||||
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_index = 0;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
// Add a single blob reference to each file
|
||||
std::string blob_index;
|
||||
BlobIndex::EncodeBlob(&blob_index, /* blob_file_number */ i + 1000,
|
||||
/* offset */ 1234, /* size */ 5678, kNoCompression);
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, Key(key_index),
|
||||
blob_index));
|
||||
ASSERT_OK(dbfull()->Write(WriteOptions(), &batch));
|
||||
|
||||
++key_index;
|
||||
|
||||
// Fill up the rest of the file with random values.
|
||||
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files_by_level;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files_by_level);
|
||||
|
||||
ASSERT_OK(options.env->GetCurrentTime(&temp_time));
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
// Keys in the SST files are distributed
|
||||
// (key000000, key000100) ->File 1
|
||||
// (key000101, key000201) -> File 2
|
||||
// (key000202, key000302) -> File 3
|
||||
// (key009999, key010099) -> File 100
|
||||
|
||||
// With keySlice (key000050, key000150) => should only pick 2 files(instead of
|
||||
// default 100 that is in the level)
|
||||
auto startKey = Slice("key000050");
|
||||
auto endKey = Slice("key000150");
|
||||
GetColumnFamilyMetaDataOptions cf_options(startKey, endKey, 0);
|
||||
db_->GetColumnFamilyMetaData(cf_options, &cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
const auto& level_meta_from_cf = cf_meta.levels[0];
|
||||
ASSERT_EQ(level_meta_from_cf.files.size(), 2);
|
||||
ASSERT_LT(level_meta_from_cf.files[1].smallestkey,
|
||||
std::string(startKey.data()));
|
||||
ASSERT_GT(level_meta_from_cf.files[0].largestkey, std::string(endKey.data()));
|
||||
|
||||
GetColumnFamilyMetaDataOptions cf_option_default;
|
||||
db_->GetColumnFamilyMetaData(cf_option_default, &cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].files.size(), 100);
|
||||
|
||||
// Test with start key valid and end key unbounded
|
||||
// This should get all files from key000150 onwards (99 files)
|
||||
auto startKeyUnbounded = Slice("key000150");
|
||||
GetColumnFamilyMetaDataOptions cf_options_unbounded_end(startKeyUnbounded,
|
||||
OptSlice(), 0);
|
||||
db_->GetColumnFamilyMetaData(cf_options_unbounded_end, &cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].files.size(), 99);
|
||||
|
||||
// Test with end key valid and start key unbounded
|
||||
// This should get all files from beginning to key000250 ( 3 files)
|
||||
auto endKeyUnbounded = Slice("key000250");
|
||||
GetColumnFamilyMetaDataOptions cf_options_unbounded_start(OptSlice(),
|
||||
endKeyUnbounded, 0);
|
||||
db_->GetColumnFamilyMetaData(cf_options_unbounded_start, &cf_meta);
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].files.size(), 3);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetColumnFamilyMetaDataBottommostLevel) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_index = 0;
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 6;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
|
||||
// Nothing on Level 0 after compaction
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
GetColumnFamilyMetaDataOptions cf_options_0(OptSlice(), OptSlice(), 0);
|
||||
db_->GetColumnFamilyMetaData(cf_options_0, &cf_meta);
|
||||
|
||||
ASSERT_EQ(cf_meta.levels.size(), 0);
|
||||
ASSERT_EQ(cf_meta.file_count, 0);
|
||||
|
||||
// Data should be in Level 6
|
||||
GetColumnFamilyMetaDataOptions cf_options(OptSlice(), OptSlice(), 6);
|
||||
db_->GetColumnFamilyMetaData(cf_options, &cf_meta);
|
||||
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].level, 6);
|
||||
ASSERT_GT(cf_meta.levels[0].files.size(), 0);
|
||||
size_t all_files = cf_meta.levels[0].files.size();
|
||||
|
||||
// Keys in the SST files are distributed across level 6
|
||||
// Test with key range - should only return files within the range
|
||||
auto startKey = Slice("key000050");
|
||||
auto endKey = Slice("key000150");
|
||||
GetColumnFamilyMetaDataOptions cf_options_range(startKey, endKey, 6);
|
||||
db_->GetColumnFamilyMetaData(cf_options_range, &cf_meta);
|
||||
|
||||
ASSERT_EQ(cf_meta.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta.levels[0].level, 6);
|
||||
ASSERT_GT(cf_meta.levels[0].files.size(), 0);
|
||||
size_t files_in_range = cf_meta.levels[0].files.size();
|
||||
|
||||
// Files in range should be less than or equal to all files
|
||||
ASSERT_LE(files_in_range, all_files);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetColumnFamilyMetaDataMultipleLevels) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_index = 0;
|
||||
|
||||
for (int i = 0; i < 50; ++i) {
|
||||
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 6;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
|
||||
for (int i = 0; i < 30; ++i) {
|
||||
GenerateNewFile(&rnd, &key_index, /* nowait */ true);
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// First verify both levels have files without key range filter
|
||||
ColumnFamilyMetaData cf_meta_all_no_range;
|
||||
GetColumnFamilyMetaDataOptions cf_options_all_no_range;
|
||||
db_->GetColumnFamilyMetaData(cf_options_all_no_range, &cf_meta_all_no_range);
|
||||
|
||||
bool has_level_0 = false;
|
||||
bool has_level_6 = false;
|
||||
for (const auto& level : cf_meta_all_no_range.levels) {
|
||||
if (level.level == 0 && level.files.size() > 0) {
|
||||
has_level_0 = true;
|
||||
}
|
||||
if (level.level == 6 && level.files.size() > 0) {
|
||||
has_level_6 = true;
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_TRUE(has_level_0);
|
||||
ASSERT_TRUE(has_level_6);
|
||||
|
||||
// Test querying bottommost level only with key range
|
||||
// Use a range that should be in the first set of files (now in level 6)
|
||||
auto startKey = Slice("key000050");
|
||||
auto endKey = Slice("key000150");
|
||||
ColumnFamilyMetaData cf_meta_bottommost;
|
||||
GetColumnFamilyMetaDataOptions cf_options_bottommost(startKey, endKey, 6);
|
||||
db_->GetColumnFamilyMetaData(cf_options_bottommost, &cf_meta_bottommost);
|
||||
|
||||
ASSERT_EQ(cf_meta_bottommost.levels.size(), 1);
|
||||
ASSERT_EQ(cf_meta_bottommost.levels[0].level, 6);
|
||||
ASSERT_GT(cf_meta_bottommost.levels[0].files.size(), 0);
|
||||
size_t level_6_files_in_range = cf_meta_bottommost.levels[0].files.size();
|
||||
|
||||
// Test querying all levels with same key range
|
||||
ColumnFamilyMetaData cf_meta_all;
|
||||
GetColumnFamilyMetaDataOptions cf_options_all(startKey, endKey);
|
||||
db_->GetColumnFamilyMetaData(cf_options_all, &cf_meta_all);
|
||||
|
||||
size_t level_6_files_in_range_from_all = 0;
|
||||
for (const auto& level : cf_meta_all.levels) {
|
||||
if (level.level == 6) {
|
||||
level_6_files_in_range_from_all = level.files.size();
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_GT(level_6_files_in_range_from_all, 0);
|
||||
ASSERT_EQ(level_6_files_in_range, level_6_files_in_range_from_all);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, GetColumnFamilyMetaDataEmptyDB) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 7;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Test on empty database
|
||||
ColumnFamilyMetaData cf_meta_empty_db;
|
||||
GetColumnFamilyMetaDataOptions cf_options_empty_db;
|
||||
db_->GetColumnFamilyMetaData(cf_options_empty_db, &cf_meta_empty_db);
|
||||
|
||||
ASSERT_EQ(cf_meta_empty_db.levels.size(), 0);
|
||||
ASSERT_EQ(cf_meta_empty_db.file_count, 0);
|
||||
ASSERT_EQ(cf_meta_empty_db.size, 0);
|
||||
|
||||
// Test on empty database with key range
|
||||
auto startKey = Slice("key000050");
|
||||
auto endKey = Slice("key000150");
|
||||
ColumnFamilyMetaData cf_meta_empty_range;
|
||||
GetColumnFamilyMetaDataOptions cf_options_empty_range(startKey, endKey);
|
||||
db_->GetColumnFamilyMetaData(cf_options_empty_range, &cf_meta_empty_range);
|
||||
|
||||
ASSERT_EQ(cf_meta_empty_range.levels.size(), 0);
|
||||
ASSERT_EQ(cf_meta_empty_range.file_count, 0);
|
||||
ASSERT_EQ(cf_meta_empty_range.size, 0);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, AllMetaDataTest) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
@@ -3535,6 +3775,11 @@ class ModelDB : public DB {
|
||||
void GetColumnFamilyMetaData(ColumnFamilyHandle* /*column_family*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) override {}
|
||||
|
||||
void GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const GetColumnFamilyMetaDataOptions& /*options*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) override {}
|
||||
|
||||
Status GetDbIdentity(std::string& /*identity*/) const override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
+12
-15
@@ -5205,6 +5205,7 @@ TEST_F(DBTest2, SwitchMemtableRaceWithNewManifest) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
options.max_manifest_file_size = 10;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.create_if_missing = true;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_EQ(2, handles_.size());
|
||||
@@ -5896,6 +5897,7 @@ TEST_P(RenameCurrentTest, Flush) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key", "value"));
|
||||
@@ -5915,6 +5917,7 @@ TEST_P(RenameCurrentTest, Compaction) {
|
||||
Destroy(last_options_);
|
||||
Options options = GetDefaultOptions();
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("a", "a_value"));
|
||||
@@ -6063,15 +6066,9 @@ 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. 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()))];
|
||||
};
|
||||
// a specific setting caused a specific outcome, in a single run. Using
|
||||
// RandomKnownTemperature() is a reasonable work-around without blowing up
|
||||
// test time.
|
||||
|
||||
auto test_fs = std::make_shared<MyTestFS>(env_->GetFileSystem());
|
||||
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, test_fs));
|
||||
@@ -6087,22 +6084,22 @@ TEST_F(DBTest2, VariousFileTemperatures) {
|
||||
options.env = env.get();
|
||||
test_fs->Reset();
|
||||
if (use_optimize) {
|
||||
test_fs->optimize_manifest_temperature = RandomTemp();
|
||||
test_fs->optimize_manifest_temperature = RandomKnownTemperature();
|
||||
test_fs->expected_manifest_temperature =
|
||||
test_fs->optimize_manifest_temperature;
|
||||
test_fs->optimize_wal_temperature = RandomTemp();
|
||||
test_fs->optimize_wal_temperature = RandomKnownTemperature();
|
||||
test_fs->expected_wal_temperature = test_fs->optimize_wal_temperature;
|
||||
}
|
||||
if (use_temp_options) {
|
||||
options.metadata_write_temperature = RandomTemp();
|
||||
options.metadata_write_temperature = RandomKnownTemperature();
|
||||
test_fs->expected_manifest_temperature =
|
||||
options.metadata_write_temperature;
|
||||
test_fs->expected_other_metadata_temperature =
|
||||
options.metadata_write_temperature;
|
||||
options.wal_write_temperature = RandomTemp();
|
||||
options.wal_write_temperature = RandomKnownTemperature();
|
||||
test_fs->expected_wal_temperature = options.wal_write_temperature;
|
||||
options.last_level_temperature = RandomTemp();
|
||||
options.default_write_temperature = RandomTemp();
|
||||
options.last_level_temperature = RandomKnownTemperature();
|
||||
options.default_write_temperature = RandomKnownTemperature();
|
||||
}
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
+13
-3
@@ -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 (getenv("ENCRYPTED_ENV")) {
|
||||
if (auto ee = getenv("ENCRYPTED_ENV")) {
|
||||
std::shared_ptr<EncryptionProvider> provider;
|
||||
std::string provider_id = getenv("ENCRYPTED_ENV");
|
||||
std::string provider_id = ee;
|
||||
if (provider_id.find('=') == std::string::npos &&
|
||||
!EndsWith(provider_id, "://test")) {
|
||||
provider_id = provider_id + "://test";
|
||||
@@ -454,7 +454,8 @@ Options DBTestBase::GetOptions(
|
||||
options.allow_mmap_reads = can_allow_mmap;
|
||||
break;
|
||||
case kManifestFileSize:
|
||||
options.max_manifest_file_size = 50; // 50 bytes
|
||||
options.max_manifest_file_size = 50; // 50 bytes
|
||||
options.max_manifest_space_amp_pct = 0; // old behavior
|
||||
break;
|
||||
case kPerfOptions:
|
||||
options.delayed_write_rate = 8 * 1024 * 1024;
|
||||
@@ -1872,4 +1873,13 @@ 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
|
||||
+21
-14
@@ -1438,20 +1438,23 @@ class DBTestBase : public testing::Test {
|
||||
std::replace(tp_string.begin(), tp_string.end(), ';', ' ');
|
||||
std::replace(tp_string.begin(), tp_string.end(), '=', ' ');
|
||||
ResetTableProperties(tp);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
private: // Prone to error on direct use
|
||||
@@ -1464,4 +1467,8 @@ 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
|
||||
+3
-2
@@ -1746,8 +1746,8 @@ class RecoveryTestHelper {
|
||||
WriteController write_controller;
|
||||
|
||||
versions.reset(new VersionSet(
|
||||
test->dbname_, &db_options, file_options, table_cache.get(),
|
||||
&write_buffer_manager, &write_controller,
|
||||
test->dbname_, &db_options, MutableDBOptions{options}, file_options,
|
||||
table_cache.get(), &write_buffer_manager, &write_controller,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"", /*db_session_id=*/"",
|
||||
options.daily_offpeak_time_utc,
|
||||
@@ -2277,6 +2277,7 @@ TEST_F(DBWALTest, FixSyncWalOnObseletedWalWithNewManifestCausingMissingWAL) {
|
||||
Options options = CurrentOptions();
|
||||
// Small size to force manifest creation
|
||||
options.max_manifest_file_size = 1;
|
||||
options.max_manifest_space_amp_pct = 0;
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
|
||||
@@ -333,6 +333,50 @@ 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,6 +129,7 @@ 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"
|
||||
|
||||
@@ -1311,7 +1311,7 @@ TEST_F(ExternalSSTFileBasicTest, SyncFailure) {
|
||||
});
|
||||
if (i == 0) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ExternalSstFileIngestionJob::CheckSyncReturnCode", [&](void* s) {
|
||||
"ExternalSstFileIngestionJob::Prepare:Reopen", [&](void* s) {
|
||||
Status* status = static_cast<Status*>(s);
|
||||
if (status->IsNotSupported()) {
|
||||
no_sync = true;
|
||||
@@ -1372,11 +1372,11 @@ TEST_F(ExternalSSTFileBasicTest, ReopenNotSupported) {
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("FileSystem::SyncFile:Open",
|
||||
[&](void* arg) {
|
||||
Status* s = static_cast<Status*>(arg);
|
||||
*s = Status::NotSupported();
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ExternalSstFileIngestionJob::Prepare:Reopen", [&](void* arg) {
|
||||
Status* s = static_cast<Status*>(arg);
|
||||
*s = Status::NotSupported();
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -42,6 +42,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -160,26 +163,35 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
// It is unsafe to assume application had sync the file and file
|
||||
// directory before ingest the file. For integrity of RocksDB we need
|
||||
// to sync the file.
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncIngestedFile");
|
||||
auto s = fs_->SyncFile(path_inside_db, env_options_, IOOptions(),
|
||||
db_options_.use_fsync, nullptr);
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::AfterSyncIngestedFile");
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"ExternalSstFileIngestionJob::CheckSyncReturnCode", &s);
|
||||
if (!s.ok()) {
|
||||
if (s.IsNotSupported()) {
|
||||
// Some file systems (especially remote/distributed) don't support
|
||||
// SyncFile API. Ignore the NotSupported error in that case.
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"After link the file, SyncFile API is not supported "
|
||||
"for file %s: %s",
|
||||
path_inside_db.c_str(), status.ToString().c_str());
|
||||
} else {
|
||||
// for other errors, propagate the error
|
||||
status = s;
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to sync ingested file %s: %s",
|
||||
path_inside_db.c_str(), status.ToString().c_str());
|
||||
|
||||
// TODO(xingbo), We should in general be moving away from production
|
||||
// uses of ReuseWritableFile (except explicitly for WAL recycling),
|
||||
// ReopenWritableFile, and NewRandomRWFile. We should create a
|
||||
// FileSystem::SyncFile/FsyncFile API that by default does the
|
||||
// re-open+sync+close combo but can (a) be reused easily, and (b) be
|
||||
// overridden to do that more cleanly, e.g. in EncryptedEnv.
|
||||
// https://github.com/facebook/rocksdb/issues/13741
|
||||
std::unique_ptr<FSWritableFile> file_to_sync;
|
||||
Status s = fs_->ReopenWritableFile(path_inside_db, env_options_,
|
||||
&file_to_sync, nullptr);
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Prepare:Reopen",
|
||||
&s);
|
||||
// Some file systems (especially remote/distributed) don't support
|
||||
// reopening a file for writing and don't require reopening and
|
||||
// syncing the file. Ignore the NotSupported error in that case.
|
||||
if (!s.IsNotSupported()) {
|
||||
status = s;
|
||||
if (status.ok()) {
|
||||
TEST_SYNC_POINT(
|
||||
"ExternalSstFileIngestionJob::BeforeSyncIngestedFile");
|
||||
status = SyncIngestedFile(file_to_sync.get());
|
||||
TEST_SYNC_POINT(
|
||||
"ExternalSstFileIngestionJob::AfterSyncIngestedFile");
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to sync ingested file %s: %s",
|
||||
path_inside_db.c_str(), status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (status.IsNotSupported() &&
|
||||
@@ -189,6 +201,10 @@ 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;
|
||||
@@ -213,6 +229,12 @@ 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
|
||||
@@ -438,6 +460,11 @@ 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) {
|
||||
@@ -472,6 +499,9 @@ 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) {
|
||||
@@ -543,6 +573,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -585,6 +618,8 @@ 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;
|
||||
}
|
||||
|
||||
@@ -607,6 +642,10 @@ 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",
|
||||
@@ -619,6 +658,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -707,8 +749,7 @@ 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,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
0 /* max_subcompaction, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
@@ -844,6 +885,10 @@ 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();
|
||||
@@ -966,6 +1011,10 @@ 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;
|
||||
}
|
||||
|
||||
@@ -990,6 +1039,9 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1006,12 +1058,19 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1025,6 +1084,10 @@ 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);
|
||||
@@ -1052,6 +1115,9 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -1243,6 +1309,9 @@ 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) {
|
||||
@@ -1355,6 +1424,14 @@ 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());
|
||||
@@ -1371,6 +1448,11 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -1403,6 +1485,9 @@ 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);
|
||||
@@ -1445,21 +1530,26 @@ Status ExternalSstFileIngestionJob::SyncIngestedFile(TWritableFile* file) {
|
||||
Status ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile(
|
||||
TableReader* table_reader, SuperVersion* sv,
|
||||
IngestedFileInfo* file_to_ingest, bool allow_data_in_errors) {
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
// 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;
|
||||
|
||||
// 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");
|
||||
SequenceNumber smallest_seqno = kMaxSequenceNumber;
|
||||
SequenceNumber largest_seqno_from_iter = 0;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
|
||||
@@ -4094,11 +4094,23 @@ 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);
|
||||
@@ -4135,7 +4147,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 =
|
||||
4 << 10; // Small files to create multiple SSTs
|
||||
20 << 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;
|
||||
@@ -4155,7 +4167,7 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
|
||||
const Snapshot* snapshot = from_db->GetSnapshot();
|
||||
|
||||
for (int k = 1; k < 99; ++k) {
|
||||
expected_values[k] = rnd->RandomString(500);
|
||||
expected_values[k] = rnd->RandomString(2000);
|
||||
ASSERT_OK(from_db->Put(wo, temp_cfh, Key(k), expected_values[k]));
|
||||
}
|
||||
ASSERT_OK(from_db->Flush({}, temp_cfh));
|
||||
@@ -4233,9 +4245,21 @@ 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;
|
||||
@@ -4250,11 +4274,17 @@ TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
|
||||
s = db_->IngestExternalFile(overlap_cf, sst_file_paths, ingest_opts);
|
||||
|
||||
ASSERT_NOK(s);
|
||||
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();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
cpu_micros);
|
||||
flush_stats.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_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, MutableDBOptions{options_}, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
test::kUnitTestDbId, /*db_session_id=*/"",
|
||||
/*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
}
|
||||
|
||||
|
||||
+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): seperate WAL related parameters from general `Reader`
|
||||
// TODO(hx235): separate 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 `Optinos::track_and_verify_wals`
|
||||
// See `Options::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 compressioned is enabled, fragment_checksum is the checksum of the
|
||||
// fragment computed from the orginal buffer containinng uncompressed
|
||||
// If WAL compression is enabled, fragment_checksum is the checksum of the
|
||||
// fragment computed from the original buffer containing 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): seperate WAL related parameters from general `Reader`
|
||||
// TODO(hx235): separate WAL related parameters from general `Reader`
|
||||
// parameters
|
||||
explicit Writer(std::unique_ptr<WritableFileWriter>&& dest,
|
||||
uint64_t log_number, bool recycle_log_files,
|
||||
|
||||
+38
-13
@@ -70,7 +70,9 @@ 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) {}
|
||||
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) {}
|
||||
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
const ImmutableOptions& ioptions,
|
||||
@@ -115,7 +117,13 @@ 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) {
|
||||
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)) {
|
||||
UpdateFlushState();
|
||||
// something went wrong if we need to flush before inserting anything
|
||||
assert(!ShouldScheduleFlush());
|
||||
@@ -394,7 +402,11 @@ class MemTableIterator : public InternalIterator {
|
||||
!mem.GetImmutableMemTableOptions()->inplace_update_support),
|
||||
arena_mode_(arena != nullptr),
|
||||
paranoid_memory_checks_(mem.moptions_.paranoid_memory_checks),
|
||||
allow_data_in_error(mem.moptions_.allow_data_in_errors) {
|
||||
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_) {
|
||||
if (kind == kRangeDelEntries) {
|
||||
iter_ = mem.range_del_table_->GetIterator(arena);
|
||||
} else if (prefix_extractor_ != nullptr &&
|
||||
@@ -463,8 +475,10 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error);
|
||||
if (validate_on_seek_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error_,
|
||||
paranoid_memory_checks_,
|
||||
key_validation_callback_);
|
||||
} else {
|
||||
iter_->Seek(k, nullptr);
|
||||
}
|
||||
@@ -488,8 +502,10 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error);
|
||||
if (validate_on_seek_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error_,
|
||||
paranoid_memory_checks_,
|
||||
key_validation_callback_);
|
||||
} else {
|
||||
iter_->Seek(k, nullptr);
|
||||
}
|
||||
@@ -518,7 +534,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_);
|
||||
@@ -540,7 +556,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();
|
||||
}
|
||||
@@ -599,7 +615,9 @@ class MemTableIterator : public InternalIterator {
|
||||
bool value_pinned_;
|
||||
bool arena_mode_;
|
||||
const bool paranoid_memory_checks_;
|
||||
const bool allow_data_in_error;
|
||||
const bool validate_on_seek_;
|
||||
const bool allow_data_in_error_;
|
||||
const std::function<Status(const char*, bool)> key_validation_callback_;
|
||||
|
||||
void VerifyEntryChecksum() {
|
||||
if (protection_bytes_per_key_ > 0 && Valid()) {
|
||||
@@ -1493,11 +1511,13 @@ 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) {
|
||||
if (!moptions_.paranoid_memory_checks &&
|
||||
!moptions_.memtable_veirfy_per_key_checksum_on_seek) {
|
||||
table_->Get(key, &saver, SaveValue);
|
||||
} else {
|
||||
Status check_s = table_->GetAndValidate(key, &saver, SaveValue,
|
||||
moptions_.allow_data_in_errors);
|
||||
Status check_s = table_->GetAndValidate(
|
||||
key, &saver, SaveValue, moptions_.allow_data_in_errors,
|
||||
moptions_.paranoid_memory_checks, key_validation_callback_);
|
||||
if (check_s.IsCorruption()) {
|
||||
*(saver.status) = check_s;
|
||||
// Should stop searching the LSM.
|
||||
@@ -1508,6 +1528,11 @@ 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,6 +64,7 @@ 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.
|
||||
@@ -826,6 +827,9 @@ 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 };
|
||||
|
||||
@@ -956,6 +960,8 @@ 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,7 +112,8 @@ class MemTableListTest : public testing::Test {
|
||||
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
|
||||
WriteController write_controller(10000000u);
|
||||
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
VersionSet versions(dbname, &immutable_db_options,
|
||||
MutableDBOptions{db_options}, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller, /*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"",
|
||||
@@ -163,7 +164,8 @@ class MemTableListTest : public testing::Test {
|
||||
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
|
||||
WriteController write_controller(10000000u);
|
||||
|
||||
VersionSet versions(dbname, &immutable_db_options, env_options,
|
||||
VersionSet versions(dbname, &immutable_db_options,
|
||||
MutableDBOptions{db_options}, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller, /*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_id=*/"",
|
||||
|
||||
+1
-1
@@ -307,7 +307,7 @@ class MergeOutputIterator {
|
||||
|
||||
Slice key() { return Slice(*it_keys_); }
|
||||
Slice value() { return Slice(*it_values_); }
|
||||
bool Valid() { return it_keys_ != merge_helper_->keys().rend(); }
|
||||
bool Valid() const { return it_keys_ != merge_helper_->keys().rend(); }
|
||||
|
||||
private:
|
||||
const MergeHelper* merge_helper_;
|
||||
|
||||
@@ -32,6 +32,7 @@ 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);
|
||||
|
||||
@@ -66,7 +67,6 @@ 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,6 +40,11 @@ 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");
|
||||
}
|
||||
|
||||
+2
-2
@@ -120,8 +120,8 @@ class Repairer {
|
||||
/*io_tracer=*/nullptr, db_session_id_)),
|
||||
wb_(db_options_.db_write_buffer_size),
|
||||
wc_(db_options_.delayed_write_rate),
|
||||
vset_(dbname_, &immutable_db_options_, file_options_,
|
||||
raw_table_cache_.get(), &wb_, &wc_,
|
||||
vset_(dbname_, &immutable_db_options_, MutableDBOptions{db_options_},
|
||||
file_options_, raw_table_cache_.get(), &wb_, &wc_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", db_session_id_, db_options.daily_offpeak_time_utc,
|
||||
/*error_handler=*/nullptr, /*read_only=*/false),
|
||||
|
||||
+478
-140
@@ -112,124 +112,9 @@ bool VersionEdit::EncodeTo(std::string* dst,
|
||||
f.epoch_number == kUnknownEpochNumber) {
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
for (const auto& blob_file_addition : blob_file_additions_) {
|
||||
@@ -288,9 +173,142 @@ 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)) {
|
||||
@@ -301,12 +319,12 @@ static bool GetInternalKey(Slice* input, InternalKey* dst) {
|
||||
}
|
||||
}
|
||||
|
||||
bool VersionEdit::GetLevel(Slice* input, int* level, const char** /*msg*/) {
|
||||
bool VersionEdit::GetLevel(Slice* input, int* level, int& max_level) {
|
||||
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 {
|
||||
@@ -314,16 +332,18 @@ bool VersionEdit::GetLevel(Slice* input, int* level, const char** /*msg*/) {
|
||||
}
|
||||
}
|
||||
|
||||
const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
const char* msg = nullptr;
|
||||
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) {
|
||||
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, &msg) && GetVarint64(input, &number) &&
|
||||
if (GetLevel(input, &level, max_level) && GetVarint64(input, &number) &&
|
||||
GetVarint64(input, &file_size) && GetInternalKey(input, &f.smallest) &&
|
||||
GetInternalKey(input, &f.largest) &&
|
||||
GetVarint64(input, &smallest_seqno) &&
|
||||
@@ -381,10 +401,10 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
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)) {
|
||||
@@ -396,7 +416,7 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
return "temperature field wrong size";
|
||||
} else {
|
||||
Temperature casted_field = static_cast<Temperature>(field[0]);
|
||||
if (casted_field <= Temperature::kCold) {
|
||||
if (casted_field < Temperature::kLastTemperature) {
|
||||
f.temperature = casted_field;
|
||||
}
|
||||
}
|
||||
@@ -436,13 +456,12 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
}
|
||||
f.fd =
|
||||
FileDescriptor(number, path_id, file_size, smallest_seqno, largest_seqno);
|
||||
new_files_.push_back(std::make_pair(level, f));
|
||||
new_files.emplace_back(level, f);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VersionEdit::EncodeFileBoundaries(std::string* dst,
|
||||
const FileMetaData& meta,
|
||||
size_t ts_sz) const {
|
||||
const FileMetaData& meta, size_t ts_sz) {
|
||||
if (ts_sz == 0 || meta.user_defined_timestamps_persisted) {
|
||||
PutLengthPrefixedSlice(dst, meta.smallest.Encode());
|
||||
PutLengthPrefixedSlice(dst, meta.largest.Encode());
|
||||
@@ -545,7 +564,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
break;
|
||||
|
||||
case kCompactCursor:
|
||||
if (GetLevel(&input, &level, &msg) && GetInternalKey(&input, &key)) {
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
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));
|
||||
@@ -558,7 +578,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
|
||||
case kDeletedFile: {
|
||||
uint64_t number = 0;
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number)) {
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetVarint64(&input, &number)) {
|
||||
deleted_files_.insert(std::make_pair(level, number));
|
||||
} else {
|
||||
if (!msg) {
|
||||
@@ -571,8 +592,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
case kNewFile: {
|
||||
uint64_t number = 0;
|
||||
uint64_t file_size = 0;
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetVarint64(&input, &number) && GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest)) {
|
||||
f.fd = FileDescriptor(number, 0, file_size);
|
||||
@@ -589,8 +610,8 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
uint64_t file_size = 0;
|
||||
SequenceNumber smallest_seqno = 0;
|
||||
SequenceNumber largest_seqno = kMaxSequenceNumber;
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetVarint64(&input, &number) && GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest) &&
|
||||
GetVarint64(&input, &smallest_seqno) &&
|
||||
@@ -612,8 +633,9 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
uint64_t file_size = 0;
|
||||
SequenceNumber smallest_seqno = 0;
|
||||
SequenceNumber largest_seqno = kMaxSequenceNumber;
|
||||
if (GetLevel(&input, &level, &msg) && GetVarint64(&input, &number) &&
|
||||
GetVarint32(&input, &path_id) && GetVarint64(&input, &file_size) &&
|
||||
if (GetLevel(&input, &level, max_level_) &&
|
||||
GetVarint64(&input, &number) && GetVarint32(&input, &path_id) &&
|
||||
GetVarint64(&input, &file_size) &&
|
||||
GetInternalKey(&input, &f.smallest) &&
|
||||
GetInternalKey(&input, &f.largest) &&
|
||||
GetVarint64(&input, &smallest_seqno) &&
|
||||
@@ -630,7 +652,10 @@ Status VersionEdit::DecodeFrom(const Slice& src) {
|
||||
}
|
||||
|
||||
case kNewFile4: {
|
||||
msg = DecodeNewFile4From(&input);
|
||||
FileMetaData ignored_file;
|
||||
msg = DecodeNewFile4From(&input, max_level_, min_log_number_to_keep_,
|
||||
has_min_log_number_to_keep_, new_files_,
|
||||
ignored_file);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -767,6 +792,23 @@ 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.
|
||||
@@ -933,6 +975,10 @@ 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;
|
||||
}
|
||||
@@ -1082,9 +1128,301 @@ 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
|
||||
+228
-8
@@ -72,6 +72,23 @@ 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 {
|
||||
@@ -440,12 +457,191 @@ 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=\"" << parsed_key.user_key.ToString(false /* hex */)
|
||||
<< "\" (hex:" << parsed_key.user_key.ToString(true /* hex */)
|
||||
<< ")";
|
||||
oss << ", seq=";
|
||||
if (parsed_key.sequence == kMaxSequenceNumber) {
|
||||
oss << "kMaxSequenceNumber";
|
||||
} else {
|
||||
oss << parsed_key.sequence;
|
||||
}
|
||||
oss << ", type=" << static_cast<int>(parsed_key.type);
|
||||
} 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) {
|
||||
@@ -564,8 +760,6 @@ 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_; }
|
||||
@@ -735,6 +929,22 @@ 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
|
||||
@@ -757,15 +967,22 @@ class VersionEdit {
|
||||
std::string DebugJSON(int edit_num, bool hex_key = false) const;
|
||||
|
||||
private:
|
||||
bool GetLevel(Slice* input, int* level, const char** msg);
|
||||
|
||||
const char* DecodeNewFile4From(Slice* input);
|
||||
|
||||
// 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);
|
||||
// Encode file boundaries `FileMetaData.smallest` and `FileMetaData.largest`.
|
||||
// User-defined timestamps in the user key will be stripped if they shouldn't
|
||||
// be persisted.
|
||||
void EncodeFileBoundaries(std::string* dst, const FileMetaData& meta,
|
||||
size_t ts_sz) const;
|
||||
static void EncodeFileBoundaries(std::string* dst, const FileMetaData& meta,
|
||||
size_t ts_sz);
|
||||
|
||||
int max_level_ = 0;
|
||||
std::string db_id_;
|
||||
@@ -816,6 +1033,9 @@ 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,6 +794,339 @@ 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) {
|
||||
|
||||
+216
-25
@@ -1101,6 +1101,27 @@ 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
|
||||
@@ -1109,6 +1130,9 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
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);
|
||||
@@ -1124,8 +1148,26 @@ class LevelIterator final : public InternalIterator {
|
||||
continue;
|
||||
}
|
||||
|
||||
InternalKey istart(start.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
InternalKey iend(end.value(), 0, kValueTypeForSeekForPrev);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -1139,14 +1181,25 @@ class LevelIterator final : public InternalIterator {
|
||||
// 3. [ S ] ...... [ E ]
|
||||
for (auto i = fstart; i <= fend; i++) {
|
||||
if (i < flevel_->num_files) {
|
||||
(*file_to_scan_opts_)[i].insert(start.value(), end.value(),
|
||||
opt.property_bag);
|
||||
// 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->num_entries == metadata->num_range_deletions) {
|
||||
// Skip range deletion only files.
|
||||
continue;
|
||||
}
|
||||
auto& args = GetMultiScanArgForFile(i);
|
||||
args.insert(start.value(), end.value(), opt.property_bag);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Propagate io colaescing threshold
|
||||
// Propagate multiscan configs
|
||||
for (auto& file_to_arg : *file_to_scan_opts_) {
|
||||
file_to_arg.second.io_coalesce_threshold = so->io_coalesce_threshold;
|
||||
file_to_arg.second.CopyConfigFrom(*so);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1222,6 +1275,10 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool OverlapRange(const ScanOptions& opts);
|
||||
#endif
|
||||
|
||||
TableCache* table_cache_;
|
||||
const ReadOptions& read_options_;
|
||||
const FileOptions& file_options_;
|
||||
@@ -1319,6 +1376,14 @@ 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
|
||||
@@ -1543,9 +1608,10 @@ 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_ && (*file_to_scan_opts_)[file_index_].size()) {
|
||||
|
||||
if (scan_opts_ && FileHasMultiScanArg(file_index_)) {
|
||||
const ScanOptions& opts =
|
||||
file_to_scan_opts_->at(file_index_).GetScanRanges().front();
|
||||
GetMultiScanArgForFile(file_index_).GetScanRanges().front();
|
||||
if (opts.range.start.has_value()) {
|
||||
InternalKey target(*opts.range.start.AsPtr(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
@@ -1595,6 +1661,19 @@ void LevelIterator::SkipEmptyFileBackward() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool LevelIterator::OverlapRange(const ScanOptions& opts) {
|
||||
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_);
|
||||
@@ -1602,12 +1681,11 @@ void LevelIterator::SetFileIterator(InternalIterator* iter) {
|
||||
|
||||
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_);
|
||||
if (FileHasMultiScanArg(file_index_)) {
|
||||
const MultiScanArgs& new_opts = GetMultiScanArgForFile(file_index_);
|
||||
assert(OverlapRange(*new_opts.GetScanRanges().begin()) &&
|
||||
OverlapRange(*new_opts.GetScanRanges().rbegin()));
|
||||
file_iter_.Prepare(&new_opts);
|
||||
} else {
|
||||
file_iter_.Prepare(scan_opts_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1946,6 +2024,79 @@ void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) {
|
||||
}
|
||||
}
|
||||
|
||||
void Version::GetColumnFamilyMetaData(
|
||||
const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* cf_meta) {
|
||||
assert(cf_meta);
|
||||
assert(cfd_);
|
||||
|
||||
cf_meta->name = cfd_->GetName();
|
||||
cf_meta->size = 0;
|
||||
cf_meta->file_count = 0;
|
||||
cf_meta->levels.clear();
|
||||
cf_meta->blob_file_size = 0;
|
||||
cf_meta->blob_file_count = 0;
|
||||
cf_meta->blob_files.clear();
|
||||
|
||||
const auto& ioptions = cfd_->ioptions();
|
||||
auto* vstorage = storage_info();
|
||||
|
||||
int first_level = (options.level >= 0) ? options.level : 0;
|
||||
int last_level =
|
||||
(options.level >= 0) ? options.level + 1 : cfd_->NumberLevels();
|
||||
|
||||
InternalKey ikey_start, ikey_end;
|
||||
const InternalKey* begin = nullptr;
|
||||
const InternalKey* end = nullptr;
|
||||
|
||||
if (options.range.start.has_value()) {
|
||||
ikey_start = InternalKey(options.range.start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
begin = &ikey_start;
|
||||
}
|
||||
|
||||
if (options.range.limit.has_value()) {
|
||||
ikey_end = InternalKey(options.range.limit.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
end = &ikey_end;
|
||||
}
|
||||
|
||||
for (int l = first_level; l < last_level; ++l) {
|
||||
uint64_t level_size = 0;
|
||||
std::vector<SstFileMetaData> files;
|
||||
std::vector<FileMetaData*> overlapping_files;
|
||||
vstorage->GetOverlappingInputs(l, begin, end, &overlapping_files);
|
||||
|
||||
for (const auto& file : overlapping_files) {
|
||||
uint32_t path_id = file->fd.GetPathId();
|
||||
const auto& file_path = (path_id < ioptions.cf_paths.size())
|
||||
? ioptions.cf_paths[path_id].path
|
||||
: ioptions.cf_paths.back().path;
|
||||
const uint64_t file_number = file->fd.GetNumber();
|
||||
files.emplace_back(
|
||||
MakeTableFileName("", file_number), file_number, file_path,
|
||||
file->fd.GetFileSize(), file->fd.smallest_seqno,
|
||||
file->fd.largest_seqno, file->smallest.user_key().ToString(),
|
||||
file->largest.user_key().ToString(),
|
||||
file->stats.num_reads_sampled.load(std::memory_order_relaxed),
|
||||
file->being_compacted, file->temperature,
|
||||
file->oldest_blob_file_number, file->TryGetOldestAncesterTime(),
|
||||
file->TryGetFileCreationTime(), file->epoch_number,
|
||||
file->file_checksum, file->file_checksum_func_name);
|
||||
files.back().num_entries = file->num_entries;
|
||||
files.back().num_deletions = file->num_deletions;
|
||||
files.back().smallest = file->smallest.Encode().ToString();
|
||||
files.back().largest = file->largest.Encode().ToString();
|
||||
level_size += file->fd.GetFileSize();
|
||||
cf_meta->file_count++;
|
||||
}
|
||||
if (!files.empty()) {
|
||||
cf_meta->levels.emplace_back(l, level_size, std::move(files));
|
||||
cf_meta->size += level_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Version::GetSstFilesSize() {
|
||||
uint64_t sst_files_size = 0;
|
||||
for (int level = 0; level < storage_info_.num_levels_; level++) {
|
||||
@@ -5229,6 +5380,7 @@ void AtomicGroupReadBuffer::Clear() {
|
||||
|
||||
VersionSet::VersionSet(
|
||||
const std::string& dbname, const ImmutableDBOptions* _db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& storage_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
@@ -5257,6 +5409,7 @@ VersionSet::VersionSet(
|
||||
prev_log_number_(0),
|
||||
current_version_number_(0),
|
||||
manifest_file_size_(0),
|
||||
last_compacted_manifest_file_size_(0),
|
||||
file_options_(storage_options),
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
io_tracer_(io_tracer),
|
||||
@@ -5264,7 +5417,9 @@ VersionSet::VersionSet(
|
||||
offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)),
|
||||
error_handler_(error_handler),
|
||||
unchanging_(unchanging),
|
||||
closed_(false) {}
|
||||
closed_(false) {
|
||||
UpdatedMutableDbOptions(mutable_db_options, /*mu=*/nullptr);
|
||||
}
|
||||
|
||||
Status VersionSet::Close(FSDirectory* db_dir, InstrumentedMutex* mu) {
|
||||
Status s;
|
||||
@@ -5360,11 +5515,35 @@ 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 (manifest_file_size_ > 0) {
|
||||
mu->AssertHeld();
|
||||
}
|
||||
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
|
||||
@@ -5608,10 +5787,11 @@ Status VersionSet::ProcessManifestWrites(
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
uint64_t prev_manifest_file_size = manifest_file_size_;
|
||||
assert(pending_manifest_file_number_ == 0);
|
||||
if (!skip_manifest_write &&
|
||||
(!descriptor_log_ ||
|
||||
manifest_file_size_ > db_options_->max_manifest_file_size)) {
|
||||
prev_manifest_file_size >= tuned_max_manifest_file_size_)) {
|
||||
TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:BeforeNewManifest");
|
||||
new_descriptor_log = true;
|
||||
} else {
|
||||
@@ -5651,6 +5831,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
IOStatus manifest_io_status;
|
||||
manifest_io_status.PermitUncheckedError();
|
||||
std::unique_ptr<log::Writer> new_desc_log_ptr;
|
||||
// Save before releasing mu
|
||||
uint64_t manifest_preallocation_size = manifest_preallocation_size_;
|
||||
if (skip_manifest_write) {
|
||||
if (s.ok()) {
|
||||
constexpr bool update_stats = true;
|
||||
@@ -5694,16 +5876,13 @@ Status VersionSet::ProcessManifestWrites(
|
||||
// This is fine because everything inside of this block is serialized --
|
||||
// only one thread can be here at the same time
|
||||
// create new manifest file
|
||||
ROCKS_LOG_INFO(db_options_->info_log, "Creating manifest %" PRIu64 "\n",
|
||||
pending_manifest_file_number_);
|
||||
std::string descriptor_fname =
|
||||
DescriptorFileName(dbname_, pending_manifest_file_number_);
|
||||
std::unique_ptr<FSWritableFile> descriptor_file;
|
||||
io_s = NewWritableFile(fs_.get(), descriptor_fname, &descriptor_file,
|
||||
opt_file_opts);
|
||||
if (io_s.ok()) {
|
||||
descriptor_file->SetPreallocationBlockSize(
|
||||
db_options_->manifest_preallocation_size);
|
||||
descriptor_file->SetPreallocationBlockSize(manifest_preallocation_size);
|
||||
FileTypeSet tmp_set = db_options_->checksum_handoff_file_types;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(descriptor_file), descriptor_fname, opt_file_opts, clock_,
|
||||
@@ -5804,6 +5983,13 @@ Status VersionSet::ProcessManifestWrites(
|
||||
if (s.ok()) {
|
||||
// find offset in manifest file where this version is stored.
|
||||
new_manifest_file_size = raw_desc_log_ptr->file()->GetFileSize();
|
||||
if (new_descriptor_log) {
|
||||
ROCKS_LOG_INFO(db_options_->info_log,
|
||||
"Created manifest %" PRIu64
|
||||
", compacted+appended from %" PRIu64 " to %" PRIu64 "\n",
|
||||
pending_manifest_file_number_, prev_manifest_file_size,
|
||||
new_manifest_file_size);
|
||||
}
|
||||
}
|
||||
|
||||
if (first_writer.edit_list.front()->IsColumnFamilyDrop()) {
|
||||
@@ -5852,6 +6038,8 @@ Status VersionSet::ProcessManifestWrites(
|
||||
descriptor_log_ = std::move(new_desc_log_ptr);
|
||||
obsolete_manifests_.emplace_back(
|
||||
DescriptorFileName("", manifest_file_number_));
|
||||
last_compacted_manifest_file_size_ = new_manifest_file_size;
|
||||
TuneMaxManifestFileSize();
|
||||
}
|
||||
|
||||
// Install the new versions
|
||||
@@ -6485,14 +6673,16 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
const ReadOptions read_options;
|
||||
const WriteOptions write_options;
|
||||
|
||||
ImmutableDBOptions db_options(*options);
|
||||
ImmutableDBOptions imm_db_options(*options);
|
||||
MutableDBOptions mutable_db_options(*options);
|
||||
ColumnFamilyOptions cf_options(*options);
|
||||
std::shared_ptr<Cache> tc(NewLRUCache(options->max_open_files - 10,
|
||||
options->table_cache_numshardbits));
|
||||
WriteController wc(options->delayed_write_rate);
|
||||
WriteBufferManager wb(options->db_write_buffer_size);
|
||||
VersionSet versions(dbname, &db_options, file_options, tc.get(), &wb, &wc,
|
||||
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/,
|
||||
VersionSet versions(dbname, &imm_db_options, mutable_db_options, file_options,
|
||||
tc.get(), &wb, &wc, nullptr /*BlockCacheTracer*/,
|
||||
nullptr /*IOTracer*/,
|
||||
/*db_id*/ "",
|
||||
/*db_session_id*/ "", options->daily_offpeak_time_utc,
|
||||
/*error_handler_*/ nullptr, /*unchanging=*/false);
|
||||
@@ -7544,12 +7734,13 @@ Status VersionSet::VerifyFileMetadata(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
ReactiveVersionSet::ReactiveVersionSet(
|
||||
const std::string& dbname, const ImmutableDBOptions* _db_options,
|
||||
const std::string& dbname, const ImmutableDBOptions* imm_db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& _file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager, WriteController* write_controller,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: VersionSet(dbname, _db_options, _file_options, table_cache,
|
||||
write_buffer_manager, write_controller,
|
||||
: VersionSet(dbname, imm_db_options, mutable_db_options, _file_options,
|
||||
table_cache, write_buffer_manager, write_controller,
|
||||
/*block_cache_tracer=*/nullptr, io_tracer, /*db_id*/ "",
|
||||
/*db_session_id*/ "", /*daily_offpeak_time_utc*/ "",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false) {}
|
||||
|
||||
+45
-5
@@ -1044,6 +1044,10 @@ class Version {
|
||||
|
||||
void GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta);
|
||||
|
||||
// Get column family metadata with optional filtering by key range and level.
|
||||
void GetColumnFamilyMetaData(const GetColumnFamilyMetaDataOptions& options,
|
||||
ColumnFamilyMetaData* cf_meta);
|
||||
|
||||
void GetSstFilesBoundaryKeys(Slice* smallest_user_key,
|
||||
Slice* largest_user_key);
|
||||
|
||||
@@ -1186,7 +1190,9 @@ class AtomicGroupReadBuffer {
|
||||
// but false for secondary instance or writable DB).
|
||||
class VersionSet {
|
||||
public:
|
||||
VersionSet(const std::string& dbname, const ImmutableDBOptions* db_options,
|
||||
VersionSet(const std::string& dbname,
|
||||
const ImmutableDBOptions* imm_db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
@@ -1203,6 +1209,13 @@ class VersionSet {
|
||||
|
||||
virtual Status Close(FSDirectory* db_dir, InstrumentedMutex* mu);
|
||||
|
||||
// Requires: already holding DB mutex `mu`, to ensure
|
||||
// * Safely read values from `updated_options`
|
||||
// * Safely update fields on `this` (must be read elsewhere while holding mu)
|
||||
// except `mu` can be nullptr during initialization
|
||||
void UpdatedMutableDbOptions(const MutableDBOptions& updated_options,
|
||||
InstrumentedMutex* mu);
|
||||
|
||||
Status LogAndApplyToDefaultColumnFamily(
|
||||
const ReadOptions& read_options, const WriteOptions& write_options,
|
||||
VersionEdit* edit, InstrumentedMutex* mu,
|
||||
@@ -1548,10 +1561,6 @@ class VersionSet {
|
||||
}
|
||||
|
||||
const FileOptions& file_options() { return file_options_; }
|
||||
void ChangeFileOptions(const MutableDBOptions& new_options) {
|
||||
file_options_.writable_file_max_buffer_size =
|
||||
new_options.writable_file_max_buffer_size;
|
||||
}
|
||||
|
||||
// TODO - Consider updating together when file options change in SetDBOptions
|
||||
const OffpeakTimeOption& offpeak_time_option() {
|
||||
@@ -1590,6 +1599,16 @@ class VersionSet {
|
||||
|
||||
bool& TEST_unchanging() { return const_cast<bool&>(unchanging_); }
|
||||
|
||||
uint64_t TEST_GetMinMaxManifestFileSize() {
|
||||
return min_max_manifest_file_size_;
|
||||
}
|
||||
unsigned TEST_GetMaxManifestSpaceAmpPct() {
|
||||
return max_manifest_space_amp_pct_;
|
||||
}
|
||||
size_t TEST_GetManifestPreallocationSize() {
|
||||
return manifest_preallocation_size_;
|
||||
}
|
||||
|
||||
protected:
|
||||
struct ManifestWriter;
|
||||
|
||||
@@ -1610,6 +1629,7 @@ class VersionSet {
|
||||
}
|
||||
};
|
||||
|
||||
// Revert back to a post-construction state (keep same options/settings)
|
||||
void Reset();
|
||||
|
||||
// Returns approximated offset of a key in a file for a given version.
|
||||
@@ -1648,6 +1668,11 @@ class VersionSet {
|
||||
ColumnFamilyData* cfd, const std::string& fpath,
|
||||
int level, const FileMetaData& meta);
|
||||
|
||||
// Auto-tune next max size for the current manifest file based on its initial
|
||||
// "compacted" size and other parameters saved in this VersionSet. Must be
|
||||
// holding DB mutex if outside of DB startup.
|
||||
void TuneMaxManifestFileSize();
|
||||
|
||||
// Protected by DB mutex.
|
||||
WalSet wals_;
|
||||
|
||||
@@ -1699,6 +1724,20 @@ class VersionSet {
|
||||
// Current size of manifest file
|
||||
uint64_t manifest_file_size_;
|
||||
|
||||
// Size of the populated manifest file last time it was re-written from
|
||||
// scratch.
|
||||
uint64_t last_compacted_manifest_file_size_;
|
||||
|
||||
// Auto-tuned max allowed size for the current manifest file
|
||||
uint64_t tuned_max_manifest_file_size_;
|
||||
|
||||
// Saved copy of max_manifest_file_size in (Mutable)DBOptions
|
||||
uint64_t min_max_manifest_file_size_;
|
||||
// Saved, sanitized copy from (Mutable)DBOptions
|
||||
unsigned max_manifest_space_amp_pct_;
|
||||
// Saved copy from (Mutable)DBOptions
|
||||
size_t manifest_preallocation_size_;
|
||||
|
||||
// Obsolete files, or during DB shutdown any files not referenced by what's
|
||||
// left of the in-memory LSM state.
|
||||
std::vector<ObsoleteFileInfo> obsolete_files_;
|
||||
@@ -1751,6 +1790,7 @@ class ReactiveVersionSet : public VersionSet {
|
||||
public:
|
||||
ReactiveVersionSet(const std::string& dbname,
|
||||
const ImmutableDBOptions* _db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& _file_options, Cache* table_cache,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller,
|
||||
|
||||
+41
-39
@@ -1159,12 +1159,12 @@ class VersionSetTestBase {
|
||||
: env_(nullptr),
|
||||
dbname_(test::PerThreadDBPath(name)),
|
||||
options_(),
|
||||
db_options_(options_),
|
||||
imm_db_options_(options_),
|
||||
cf_options_(options_),
|
||||
immutable_options_(db_options_, cf_options_),
|
||||
immutable_options_(imm_db_options_, cf_options_),
|
||||
mutable_cf_options_(cf_options_),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
write_buffer_manager_(imm_db_options_.db_write_buffer_size),
|
||||
shutting_down_(false),
|
||||
table_factory_(std::make_shared<mock::MockTableFactory>()) {
|
||||
EXPECT_OK(test::CreateEnvFromSystem(ConfigOptions(), &env_, &env_guard_));
|
||||
@@ -1178,8 +1178,8 @@ class VersionSetTestBase {
|
||||
EXPECT_OK(fs_->CreateDirIfMissing(dbname_, IOOptions(), nullptr));
|
||||
|
||||
options_.env = env_;
|
||||
db_options_.env = env_;
|
||||
db_options_.fs = fs_;
|
||||
imm_db_options_.env = env_;
|
||||
imm_db_options_.fs = fs_;
|
||||
immutable_options_.env = env_;
|
||||
immutable_options_.fs = fs_;
|
||||
immutable_options_.clock = env_->GetSystemClock().get();
|
||||
@@ -1188,16 +1188,17 @@ class VersionSetTestBase {
|
||||
mutable_cf_options_.table_factory = table_factory_;
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
reactive_versions_ = std::make_shared<ReactiveVersionSet>(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_, nullptr);
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
nullptr);
|
||||
imm_db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
}
|
||||
|
||||
virtual ~VersionSetTestBase() {
|
||||
@@ -1220,7 +1221,7 @@ class VersionSetTestBase {
|
||||
ASSERT_OK(
|
||||
SetIdentityFile(WriteOptions(), env_, dbname_, Temperature::kUnknown));
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
if (imm_db_options_.write_dbid_to_manifest) {
|
||||
DBOptions tmp_db_options;
|
||||
tmp_db_options.env = env_;
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
|
||||
@@ -1381,8 +1382,8 @@ class VersionSetTestBase {
|
||||
|
||||
void ReopenDB() {
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
@@ -1471,7 +1472,8 @@ class VersionSetTestBase {
|
||||
const std::string dbname_;
|
||||
EnvOptions env_options_;
|
||||
Options options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
ImmutableDBOptions imm_db_options_;
|
||||
MutableDBOptions mutable_db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
ImmutableOptions immutable_options_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
@@ -1902,8 +1904,8 @@ TEST_F(VersionSetTest, WalAddition) {
|
||||
// Recover a new VersionSet.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -1970,8 +1972,8 @@ TEST_F(VersionSetTest, WalCloseWithoutSync) {
|
||||
// Recover a new VersionSet.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2024,8 +2026,8 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
// Recover a new VersionSet, only the non-closed WAL should show up.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2063,8 +2065,8 @@ TEST_F(VersionSetTest, WalDeletion) {
|
||||
// Recover from the new MANIFEST, only the non-closed WAL should show up.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2184,8 +2186,8 @@ TEST_F(VersionSetTest, DeleteWalsBeforeNonExistingWalNumber) {
|
||||
// Recover a new VersionSet, WAL0 is deleted, WAL1 is not.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2221,8 +2223,8 @@ TEST_F(VersionSetTest, DeleteAllWals) {
|
||||
// Recover a new VersionSet, all WALs are deleted.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2264,8 +2266,8 @@ TEST_F(VersionSetTest, AtomicGroupWithWalEdits) {
|
||||
// kept.
|
||||
{
|
||||
std::unique_ptr<VersionSet> new_versions(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -2444,8 +2446,8 @@ class VersionSetWithTimestampTest : public VersionSetTest {
|
||||
|
||||
void VerifyFullHistoryTsLow(uint64_t expected_ts_low) {
|
||||
std::unique_ptr<VersionSet> vset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &imm_db_options_, mutable_db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*unchanging=*/false));
|
||||
@@ -3500,7 +3502,7 @@ class VersionSetTestEmptyDb
|
||||
std::unique_ptr<log::Writer>* log_writer) override {
|
||||
assert(nullptr != log_writer);
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
if (imm_db_options_.write_dbid_to_manifest) {
|
||||
ASSERT_OK(SetIdentityFile(WriteOptions(), env_, dbname_,
|
||||
Temperature::kUnknown));
|
||||
DBOptions tmp_db_options;
|
||||
@@ -3532,7 +3534,7 @@ class VersionSetTestEmptyDb
|
||||
const std::string VersionSetTestEmptyDb::kUnknownColumnFamilyName = "unknown";
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
log_writer_.reset();
|
||||
CreateCurrentFile();
|
||||
@@ -3564,7 +3566,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest0) {
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Only a subset of column families in the MANIFEST.
|
||||
VersionEdit new_cf1;
|
||||
@@ -3605,7 +3607,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromIncompleteManifest1) {
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
@@ -3651,7 +3653,7 @@ TEST_P(VersionSetTestEmptyDb, OpenFromInCompleteManifest2) {
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
@@ -3708,7 +3710,7 @@ TEST_P(VersionSetTestEmptyDb, OpenManifestWithUnknownCF) {
|
||||
}
|
||||
|
||||
TEST_P(VersionSetTestEmptyDb, OpenCompleteManifest) {
|
||||
db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
imm_db_options_.write_dbid_to_manifest = std::get<0>(GetParam());
|
||||
PrepareManifest(nullptr, nullptr, &log_writer_);
|
||||
// Write all column families but no log_number, next_file_number and
|
||||
// last_sequence.
|
||||
@@ -3828,7 +3830,7 @@ class VersionSetTestMissingFiles : public VersionSetTestBase,
|
||||
ASSERT_OK(s);
|
||||
log_writer->reset(new log::Writer(std::move(file_writer), 0, false));
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
if (imm_db_options_.write_dbid_to_manifest) {
|
||||
DBOptions tmp_db_options;
|
||||
tmp_db_options.env = env_;
|
||||
std::unique_ptr<DBImpl> impl(new DBImpl(tmp_db_options, dbname_));
|
||||
@@ -4088,7 +4090,7 @@ TEST_F(VersionSetTestMissingFiles, NoFileMissing) {
|
||||
}
|
||||
|
||||
TEST_F(VersionSetTestMissingFiles, MinLogNumberToKeep2PC) {
|
||||
db_options_.allow_2pc = true;
|
||||
imm_db_options_.allow_2pc = true;
|
||||
NewDB();
|
||||
|
||||
SstInfo sst(100, kDefaultColumnFamilyName, "a", 0 /* level */,
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
@@ -23,7 +23,8 @@ class OfflineManifestWriter {
|
||||
immutable_db_options_(WithDbPath(options, db_path)),
|
||||
tc_(NewLRUCache(1 << 20 /* capacity */,
|
||||
options.table_cache_numshardbits)),
|
||||
versions_(db_path, &immutable_db_options_, sopt_, tc_.get(), &wb_, &wc_,
|
||||
versions_(db_path, &immutable_db_options_, MutableDBOptions{options},
|
||||
sopt_, tc_.get(), &wb_, &wc_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"",
|
||||
options.daily_offpeak_time_utc,
|
||||
|
||||
@@ -50,8 +50,8 @@ class WalManagerTest : public testing::Test {
|
||||
db_options_.clock = env_->GetSystemClock().get();
|
||||
|
||||
versions_.reset(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
dbname_, &db_options_, MutableDBOptions{}, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_id=*/"", /*db_session_id=*/"", /*daily_offpeak_time_utc=*/"",
|
||||
/*error_handler=*/nullptr, /*read_only=*/false));
|
||||
|
||||
+1
-1
@@ -179,7 +179,7 @@ class WriteThread {
|
||||
PostMemTableCallback* _post_memtable_callback = nullptr,
|
||||
bool _ingest_wbwi = false)
|
||||
: batch(_batch),
|
||||
// TODO: store a copy of WriteOptions instead of its seperated data
|
||||
// TODO: store a copy of WriteOptions instead of its separated data
|
||||
// members
|
||||
sync(write_options.sync),
|
||||
no_slowdown(write_options.no_slowdown),
|
||||
|
||||
@@ -2,6 +2,7 @@ add_executable(db_stress${ARTIFACT_SUFFIX}
|
||||
batched_ops_stress.cc
|
||||
cf_consistency_stress.cc
|
||||
db_stress.cc
|
||||
db_stress_compaction_service.cc
|
||||
db_stress_compression_manager.cc
|
||||
db_stress_common.cc
|
||||
db_stress_driver.cc
|
||||
@@ -9,7 +10,6 @@ add_executable(db_stress${ARTIFACT_SUFFIX}
|
||||
db_stress_gflags.cc
|
||||
db_stress_listener.cc
|
||||
db_stress_shared_state.cc
|
||||
db_stress_stat.cc
|
||||
db_stress_test_base.cc
|
||||
db_stress_wide_merge_operator.cc
|
||||
db_stress_tool.cc
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "file/file_util.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/file_checksum_helper.h"
|
||||
#include "util/xxhash.h"
|
||||
@@ -228,14 +229,250 @@ void CompressedCacheSetCapacityThread(void* v) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
static void SetupFaultInjectionForRemoteCompaction(SharedState* shared) {
|
||||
if (!fault_fs_guard) {
|
||||
return;
|
||||
}
|
||||
|
||||
fault_fs_guard->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kRead, shared->GetSeed(), FLAGS_read_fault_one_in,
|
||||
FLAGS_inject_error_severity == 1 /* retryable */,
|
||||
FLAGS_inject_error_severity == 2 /* has_data_loss*/);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(FaultInjectionIOType::kRead);
|
||||
|
||||
fault_fs_guard->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kWrite, shared->GetSeed(), FLAGS_write_fault_one_in,
|
||||
FLAGS_inject_error_severity == 1 /* retryable */,
|
||||
FLAGS_inject_error_severity == 2 /* has_data_loss*/);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(FaultInjectionIOType::kWrite);
|
||||
|
||||
fault_fs_guard->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kMetadataRead, shared->GetSeed(),
|
||||
FLAGS_metadata_read_fault_one_in,
|
||||
FLAGS_inject_error_severity == 1 /* retryable */,
|
||||
FLAGS_inject_error_severity == 2 /* has_data_loss*/);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataRead);
|
||||
|
||||
fault_fs_guard->SetThreadLocalErrorContext(
|
||||
FaultInjectionIOType::kMetadataWrite, shared->GetSeed(),
|
||||
FLAGS_metadata_write_fault_one_in,
|
||||
FLAGS_inject_error_severity == 1 /* retryable */,
|
||||
FLAGS_inject_error_severity == 2 /* has_data_loss*/);
|
||||
fault_fs_guard->EnableThreadLocalErrorInjection(
|
||||
FaultInjectionIOType::kMetadataWrite);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
static CompactionServiceOptionsOverride CreateOverrideOptions(
|
||||
const Options& options, const CompactionServiceJobInfo& job_info) {
|
||||
CompactionServiceOptionsOverride override_options{
|
||||
.env = db_stress_env,
|
||||
.file_checksum_gen_factory = options.file_checksum_gen_factory,
|
||||
.merge_operator = options.merge_operator,
|
||||
.compaction_filter = options.compaction_filter,
|
||||
.compaction_filter_factory = options.compaction_filter_factory,
|
||||
.prefix_extractor = options.prefix_extractor,
|
||||
.sst_partitioner_factory = options.sst_partitioner_factory,
|
||||
.listeners = options.listeners,
|
||||
.statistics = options.statistics,
|
||||
.table_properties_collector_factories =
|
||||
options.table_properties_collector_factories};
|
||||
|
||||
// TODO(jaykorean) - create a new compaction filter / merge operator and
|
||||
// others for remote compactions
|
||||
//
|
||||
// Create a new Table Factory
|
||||
ConfigOptions config_options;
|
||||
config_options.ignore_unknown_options = false;
|
||||
config_options.ignore_unsupported_options = false;
|
||||
|
||||
Status s = TableFactory::CreateFromString(config_options,
|
||||
options.table_factory->Name(),
|
||||
&override_options.table_factory);
|
||||
|
||||
if (s.ok()) {
|
||||
std::string options_str;
|
||||
s = options.table_factory->GetOptionString(config_options, &options_str);
|
||||
if (s.ok()) {
|
||||
s = override_options.table_factory->ConfigureFromString(config_options,
|
||||
options_str);
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
fprintf(stdout,
|
||||
"Failed to set up TableFactory for remote compaction - (%s): %s\n",
|
||||
job_info.db_name.c_str(), s.ToString().c_str());
|
||||
}
|
||||
|
||||
return override_options;
|
||||
}
|
||||
|
||||
static Status CleanupOutputDirectory(const std::string& output_directory) {
|
||||
#ifndef NDEBUG
|
||||
// Temporarily disable fault injection to ensure deletion always succeeds
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->DisableAllThreadLocalErrorInjection();
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
Status s = DestroyDir(db_stress_env, output_directory);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr,
|
||||
"Failed to destroy output directory %s when allow_resumption is "
|
||||
"false: %s\n",
|
||||
output_directory.c_str(), s.ToString().c_str());
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
s = db_stress_env->CreateDir(output_directory);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr,
|
||||
"Failed to recreate output directory %s when allow_resumption is "
|
||||
"false: %s\n",
|
||||
output_directory.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Re-enable fault injection after deletion
|
||||
if (fault_fs_guard) {
|
||||
fault_fs_guard->EnableAllThreadLocalErrorInjection();
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Set up cancellation mechanism for testing resumable remote compactions.
|
||||
// Spawns a detached thread to trigger cancellation after a delay (50ms
|
||||
// initially, or 2/3 of the previous successful compaction time for adaptive
|
||||
// timing). First-time jobs are always canceled; retries have a 10% chance
|
||||
// to test consecutive cancellation scenarios.
|
||||
static std::shared_ptr<std::atomic<bool>> SetupCancellation(
|
||||
OpenAndCompactOptions& open_compact_options, bool was_canceled,
|
||||
Random& rand, uint64_t successful_compaction_end_to_end_micros) {
|
||||
auto canceled = std::make_shared<std::atomic<bool>>(false);
|
||||
open_compact_options.canceled = canceled.get();
|
||||
|
||||
bool should_cancel = !was_canceled || rand.OneIn(10);
|
||||
|
||||
if (should_cancel) {
|
||||
std::thread interruption_thread(
|
||||
[canceled, successful_compaction_end_to_end_micros]() {
|
||||
uint64_t sleep_micros =
|
||||
successful_compaction_end_to_end_micros == 0
|
||||
? 50000
|
||||
: successful_compaction_end_to_end_micros * 2 / 3;
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(sleep_micros));
|
||||
canceled->store(true);
|
||||
});
|
||||
interruption_thread.detach();
|
||||
}
|
||||
|
||||
return canceled;
|
||||
}
|
||||
|
||||
// Process the result of OpenAndCompact operation
|
||||
static void ProcessCompactionResult(
|
||||
const Status& s, const std::string& job_id,
|
||||
const CompactionServiceJobInfo& job_info,
|
||||
const std::string& serialized_input, const std::string& output_directory,
|
||||
const std::string& serialized_output, SharedState* shared,
|
||||
uint64_t& successful_compaction_end_to_end_micros, uint64_t start_micros,
|
||||
Env* env) {
|
||||
if (s.IsManualCompactionPaused() && FLAGS_allow_resumption_one_in > 0) {
|
||||
// Re-enqueue for retry
|
||||
shared->EnqueueRemoteCompaction(job_id, job_info, serialized_input,
|
||||
output_directory, true /* was_cancelled */);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
if (!StressTest::IsErrorInjectedAndRetryable(s)) {
|
||||
// Print in stdout instead of stderr to avoid stress test failure,
|
||||
// because OpenAndCompact() failure doesn't necessarily mean
|
||||
// primary db instance failure.
|
||||
fprintf(stdout, "Failed to run OpenAndCompact(%s): %s\n",
|
||||
job_info.db_name.c_str(), s.ToString().c_str());
|
||||
}
|
||||
} else {
|
||||
// Track successful completion time
|
||||
successful_compaction_end_to_end_micros = env->NowMicros() - start_micros;
|
||||
}
|
||||
|
||||
// Add the output regardless of status, so that primary DB doesn't rely
|
||||
// on the timeout to finish waiting. The actual failure from the
|
||||
// deserialization can fail the compaction properly
|
||||
shared->AddRemoteCompactionResult(job_id, s, serialized_output);
|
||||
}
|
||||
|
||||
static void ProcessRemoteCompactionJob(
|
||||
const std::string& job_id, const CompactionServiceJobInfo& job_info,
|
||||
const std::string& serialized_input, const std::string& output_directory,
|
||||
bool was_canceled, SharedState* shared, StressTest* stress_test,
|
||||
Random& rand, uint64_t& successful_compaction_end_to_end_micros) {
|
||||
auto options = stress_test->GetOptions(job_info.cf_id);
|
||||
assert(options.env != nullptr);
|
||||
|
||||
auto override_options = CreateOverrideOptions(options, job_info);
|
||||
|
||||
OpenAndCompactOptions open_compact_options;
|
||||
if (FLAGS_allow_resumption_one_in > 0) {
|
||||
open_compact_options.allow_resumption =
|
||||
rand.OneIn(FLAGS_allow_resumption_one_in);
|
||||
} else {
|
||||
open_compact_options.allow_resumption = false;
|
||||
}
|
||||
|
||||
if (!open_compact_options.allow_resumption) {
|
||||
CleanupOutputDirectory(output_directory);
|
||||
}
|
||||
|
||||
std::shared_ptr<std::atomic<bool>> canceled = nullptr;
|
||||
if (FLAGS_allow_resumption_one_in > 0) {
|
||||
canceled = SetupCancellation(open_compact_options, was_canceled, rand,
|
||||
successful_compaction_end_to_end_micros);
|
||||
}
|
||||
|
||||
std::string serialized_output;
|
||||
uint64_t start_micros = options.env->NowMicros();
|
||||
|
||||
Status s = DB::OpenAndCompact(open_compact_options, job_info.db_name,
|
||||
output_directory, serialized_input,
|
||||
&serialized_output, override_options);
|
||||
|
||||
ProcessCompactionResult(s, job_id, job_info, serialized_input,
|
||||
output_directory, serialized_output, shared,
|
||||
successful_compaction_end_to_end_micros, start_micros,
|
||||
options.env);
|
||||
}
|
||||
|
||||
void RemoteCompactionWorkerThread(void* v) {
|
||||
assert(FLAGS_remote_compaction_worker_threads > 0);
|
||||
assert(FLAGS_remote_compaction_worker_interval > 0);
|
||||
|
||||
auto* thread = static_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
StressTest* stress_test = shared->GetStressTest();
|
||||
assert(stress_test != nullptr);
|
||||
|
||||
#ifndef NDEBUG
|
||||
SetupFaultInjectionForRemoteCompaction(shared);
|
||||
#endif // NDEBUG
|
||||
|
||||
// Tracks the duration (in microseconds) of the most recent successfully
|
||||
// completed compaction from start to finish. This value is used in
|
||||
// SetupCancellation() to adaptively set up cancellation point for a
|
||||
// compaction
|
||||
uint64_t successful_compaction_end_to_end_micros = 0;
|
||||
Random rand(static_cast<uint32_t>(FLAGS_seed));
|
||||
|
||||
// Main worker loop
|
||||
while (true) {
|
||||
// Check if we should stop
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
if (shared->ShouldStopBgThread()) {
|
||||
@@ -246,42 +483,20 @@ void RemoteCompactionWorkerThread(void* v) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string job_id;
|
||||
CompactionServiceJobInfo job_info;
|
||||
std::string serialized_input;
|
||||
if (shared->DequeueRemoteCompaction(&job_id, &job_info,
|
||||
&serialized_input)) {
|
||||
auto options = stress_test->GetOptions(job_info.cf_id);
|
||||
CompactionServiceOptionsOverride override_options{
|
||||
.file_checksum_gen_factory = options.file_checksum_gen_factory,
|
||||
.merge_operator = options.merge_operator,
|
||||
.compaction_filter = options.compaction_filter,
|
||||
.compaction_filter_factory = options.compaction_filter_factory,
|
||||
.prefix_extractor = options.prefix_extractor,
|
||||
.table_factory = options.table_factory,
|
||||
.sst_partitioner_factory = options.sst_partitioner_factory,
|
||||
.listeners = {},
|
||||
.statistics = options.statistics,
|
||||
.table_properties_collector_factories =
|
||||
options.table_properties_collector_factories};
|
||||
std::string tmp_output_dir = job_info.db_name + "/" + "tmp_output_" +
|
||||
db_stress_env->GenerateUniqueId();
|
||||
std::string serialized_output;
|
||||
Status s = DB::OpenAndCompact(OpenAndCompactOptions{}, job_info.db_name,
|
||||
tmp_output_dir, serialized_input,
|
||||
&serialized_output, override_options);
|
||||
if (!s.ok()) {
|
||||
// Print in stdout instead of stderr to avoid stress test failure,
|
||||
// because OpenAndCompact() failure doesn't necessarily mean
|
||||
// primary db instance failure.
|
||||
fprintf(stdout, "Failed to run OpenAndCompact(%s): %s\n",
|
||||
job_info.db_name.c_str(), s.ToString().c_str());
|
||||
}
|
||||
// Add the output regardless of status, so that primary DB doesn't rely on
|
||||
// the timeout to finish waiting. The actual failure from the
|
||||
// deserialization can fail the compaction properly
|
||||
shared->AddRemoteCompactionResult(job_id, serialized_output);
|
||||
std::string output_directory;
|
||||
bool was_canceled;
|
||||
|
||||
if (shared->DequeueRemoteCompaction(&job_id, &job_info, &serialized_input,
|
||||
&output_directory, &was_canceled)) {
|
||||
ProcessRemoteCompactionJob(
|
||||
job_id, job_info, serialized_input, output_directory, was_canceled,
|
||||
shared, stress_test, rand, successful_compaction_end_to_end_micros);
|
||||
}
|
||||
|
||||
db_stress_env->SleepForMicroseconds(
|
||||
thread->rand.Next() % FLAGS_remote_compaction_worker_interval * 1000 +
|
||||
1);
|
||||
|
||||
@@ -248,6 +248,7 @@ DECLARE_string(fs_uri);
|
||||
DECLARE_uint64(ops_per_thread);
|
||||
DECLARE_uint64(log2_keys_per_lock);
|
||||
DECLARE_uint64(max_manifest_file_size);
|
||||
DECLARE_int32(max_manifest_space_amp_pct);
|
||||
DECLARE_bool(in_place_update);
|
||||
DECLARE_string(memtablerep);
|
||||
DECLARE_int32(prefix_size);
|
||||
@@ -275,6 +276,7 @@ DECLARE_string(last_level_temperature);
|
||||
DECLARE_string(default_write_temperature);
|
||||
DECLARE_string(default_temperature);
|
||||
DECLARE_bool(paranoid_memory_checks);
|
||||
DECLARE_bool(memtable_veirfy_per_key_checksum_on_seek);
|
||||
|
||||
// Options for transaction dbs.
|
||||
// Use TransactionDB (a.k.a. Pessimistic Transaction DB)
|
||||
@@ -284,6 +286,7 @@ DECLARE_bool(use_txn);
|
||||
// Options for TransactionDB (a.k.a. Pessimistic Transaction DB)
|
||||
DECLARE_uint64(txn_write_policy);
|
||||
DECLARE_bool(unordered_write);
|
||||
DECLARE_bool(use_per_key_point_lock_mgr);
|
||||
|
||||
// Options for OptimisticTransactionDB
|
||||
DECLARE_bool(use_optimistic_txn);
|
||||
@@ -408,6 +411,8 @@ DECLARE_uint64(max_sequential_skip_in_iterations);
|
||||
DECLARE_bool(enable_sst_partitioner_factory);
|
||||
DECLARE_bool(enable_do_not_compress_roles);
|
||||
DECLARE_bool(block_align);
|
||||
DECLARE_uint64(super_block_alignment_size);
|
||||
DECLARE_uint64(super_block_alignment_space_overhead_ratio);
|
||||
DECLARE_uint32(lowest_used_cache_tier);
|
||||
DECLARE_bool(enable_custom_split_merge);
|
||||
DECLARE_uint32(adm_policy);
|
||||
@@ -424,12 +429,22 @@ DECLARE_uint32(commit_bypass_memtable_one_in);
|
||||
DECLARE_bool(track_and_verify_wals);
|
||||
DECLARE_int32(remote_compaction_worker_threads);
|
||||
DECLARE_int32(remote_compaction_worker_interval);
|
||||
DECLARE_bool(remote_compaction_failure_fall_back_to_local);
|
||||
DECLARE_int32(allow_resumption_one_in);
|
||||
DECLARE_bool(auto_refresh_iterator_with_snapshot);
|
||||
DECLARE_uint32(memtable_op_scan_flush_trigger);
|
||||
DECLARE_uint32(memtable_avg_op_scan_flush_trigger);
|
||||
DECLARE_uint32(ingest_wbwi_one_in);
|
||||
DECLARE_bool(universal_reduce_file_locking);
|
||||
DECLARE_bool(use_multiscan);
|
||||
DECLARE_bool(multiscan_use_async_io);
|
||||
|
||||
// Compaction deletion trigger declarations for stress testing
|
||||
DECLARE_bool(enable_compaction_on_deletion_trigger);
|
||||
DECLARE_uint64(compaction_on_deletion_min_file_size);
|
||||
DECLARE_int32(compaction_on_deletion_trigger_count);
|
||||
DECLARE_int32(compaction_on_deletion_window_size);
|
||||
DECLARE_double(compaction_on_deletion_ratio);
|
||||
|
||||
constexpr long KB = 1024;
|
||||
constexpr int kRandomValueMaxFactor = 3;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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).
|
||||
|
||||
#ifdef GFLAGS
|
||||
|
||||
#include "db_stress_tool/db_stress_compaction_service.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "db_stress_tool/db_stress_test_base.h"
|
||||
#include "rocksdb/env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
CompactionServiceJobStatus DbStressCompactionService::Wait(
|
||||
const std::string& scheduled_job_id, std::string* result) {
|
||||
while (true) {
|
||||
if (aborted_.load()) {
|
||||
return CompactionServiceJobStatus::kAborted;
|
||||
}
|
||||
const auto& maybeResultStatus =
|
||||
shared_->GetRemoteCompactionResult(scheduled_job_id, result);
|
||||
if (maybeResultStatus.has_value()) {
|
||||
auto s = maybeResultStatus.value();
|
||||
if (s.ok()) {
|
||||
assert(result);
|
||||
assert(!result->empty());
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
} else {
|
||||
// Remote Compaction failed
|
||||
if (failure_should_fall_back_to_local_) {
|
||||
return CompactionServiceJobStatus::kUseLocal;
|
||||
}
|
||||
if (StressTest::IsErrorInjectedAndRetryable(s)) {
|
||||
return CompactionServiceJobStatus::kUseLocal;
|
||||
}
|
||||
if (result && result->empty()) {
|
||||
// If result is empty, set the compaction status in the result so
|
||||
// that it can be bubbled up to main thread
|
||||
CompactionServiceResult compaction_result;
|
||||
compaction_result.status = s;
|
||||
if (compaction_result.Write(result).ok()) {
|
||||
assert(result);
|
||||
assert(!result->empty());
|
||||
}
|
||||
}
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
} else {
|
||||
// Remote Compaction is still running
|
||||
Env::Default()->SleepForMicroseconds(kWaitIntervalInMicros);
|
||||
}
|
||||
}
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // GFLAGS
|
||||
@@ -3,61 +3,57 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#ifdef GFLAGS
|
||||
#pragma once
|
||||
|
||||
#include "db/compaction/compaction_job.h"
|
||||
#include "db_stress_shared_state.h"
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Service to simulate Remote Compaction in Stress Test
|
||||
class DbStressCompactionService : public CompactionService {
|
||||
public:
|
||||
explicit DbStressCompactionService(SharedState* shared)
|
||||
: shared_(shared), aborted_(false) {}
|
||||
explicit DbStressCompactionService(SharedState* shared,
|
||||
bool failure_should_fall_back_to_local)
|
||||
: shared_(shared),
|
||||
aborted_(false),
|
||||
failure_should_fall_back_to_local_(failure_should_fall_back_to_local) {}
|
||||
|
||||
static const char* kClassName() { return "DbStressCompactionService"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
static constexpr uint64_t kWaitIntervalInMicros = 10 * 1000; // 10ms
|
||||
static constexpr uint64_t kWaitTimeoutInMicros =
|
||||
30 * 1000 * 1000; // 30 seconds
|
||||
|
||||
static constexpr const char* kTempOutputDirectoryPrefix = "tmp_output_";
|
||||
|
||||
CompactionServiceScheduleResponse Schedule(
|
||||
const CompactionServiceJobInfo& info,
|
||||
const std::string& compaction_service_input) override {
|
||||
std::string job_id = info.db_id + "_" + info.db_session_id + "_" +
|
||||
std::to_string(info.job_id);
|
||||
|
||||
if (aborted_.load()) {
|
||||
return CompactionServiceScheduleResponse(
|
||||
job_id, CompactionServiceJobStatus::kUseLocal);
|
||||
}
|
||||
shared_->EnqueueRemoteCompaction(job_id, info, compaction_service_input);
|
||||
std::string output_directory = info.db_name + "/" +
|
||||
kTempOutputDirectoryPrefix +
|
||||
Env::Default()->GenerateUniqueId();
|
||||
|
||||
shared_->EnqueueRemoteCompaction(
|
||||
job_id, info, compaction_service_input, output_directory,
|
||||
false /* was_cancelled */); // Not canceled initially
|
||||
CompactionServiceScheduleResponse response(
|
||||
job_id, CompactionServiceJobStatus::kSuccess);
|
||||
return response;
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus Wait(const std::string& scheduled_job_id,
|
||||
std::string* result) override {
|
||||
auto start = Env::Default()->NowMicros();
|
||||
while (Env::Default()->NowMicros() - start < kWaitTimeoutInMicros) {
|
||||
if (aborted_.load()) {
|
||||
return CompactionServiceJobStatus::kUseLocal;
|
||||
}
|
||||
if (shared_->GetRemoteCompactionResult(scheduled_job_id, result).ok()) {
|
||||
if (result && result->empty()) {
|
||||
// Race: Remote worker aborted before client sets aborted_ = true
|
||||
return CompactionServiceJobStatus::kUseLocal;
|
||||
}
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
}
|
||||
Env::Default()->SleepForMicroseconds(kWaitIntervalInMicros);
|
||||
}
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
std::string* result) override;
|
||||
|
||||
void OnInstallation(const std::string& scheduled_job_id,
|
||||
CompactionServiceJobStatus /*status*/) override {
|
||||
@@ -65,7 +61,7 @@ class DbStressCompactionService : public CompactionService {
|
||||
std::string serialized;
|
||||
CompactionServiceResult result;
|
||||
if (shared_->GetRemoteCompactionResult(scheduled_job_id, &serialized)
|
||||
.ok()) {
|
||||
.has_value()) {
|
||||
if (CompactionServiceResult::Read(serialized, &result).ok()) {
|
||||
std::vector<std::string> filenames;
|
||||
Status s = Env::Default()->GetChildren(result.output_path, &filenames);
|
||||
@@ -90,6 +86,8 @@ class DbStressCompactionService : public CompactionService {
|
||||
private:
|
||||
SharedState* shared_;
|
||||
std::atomic_bool aborted_{false};
|
||||
bool failure_should_fall_back_to_local_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // GFLAGS
|
||||
Loaded 100 of 273 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user