mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
323
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 | ||
|
|
82b5a2d3fc | ||
|
|
439e1707fc | ||
|
|
239b06cefb | ||
|
|
b9957c991c | ||
|
|
444f1ed07f | ||
|
|
a5d4db64e2 | ||
|
|
0b426ff58d | ||
|
|
618f660eab | ||
|
|
84f814454a | ||
|
|
772e342a92 | ||
|
|
b3fdb9b3cc | ||
|
|
5c7162da27 | ||
|
|
972fd9adf1 | ||
|
|
1369c7b169 | ||
|
|
7e9c96020b | ||
|
|
8f0ab1598e | ||
|
|
e12734d51f | ||
|
|
99bbc2d7fa | ||
|
|
496eebaee8 | ||
|
|
d8835f918c | ||
|
|
13f054febb | ||
|
|
0b44282a9d | ||
|
|
b8b42b7a68 | ||
|
|
d2ac955881 | ||
|
|
b43a84fc37 | ||
|
|
d0051d9314 | ||
|
|
f2b646713e | ||
|
|
1bba680ebb | ||
|
|
3dd6c6f9cb | ||
|
|
dfb4efaae3 | ||
|
|
9c0a0c0058 | ||
|
|
3bd7d968e1 | ||
|
|
d0a412d962 | ||
|
|
b6e804b7de | ||
|
|
799079cac5 | ||
|
|
a88d367096 | ||
|
|
53c39c2b01 | ||
|
|
7c5c37a1a4 | ||
|
|
3829750b70 | ||
|
|
ccd850fa56 | ||
|
|
0a169cea0e | ||
|
|
7f14960816 | ||
|
|
3757e5479d | ||
|
|
e7a4505a2e | ||
|
|
5435032c4c | ||
|
|
f66ac76938 | ||
|
|
07f1520290 | ||
|
|
f8535fb955 | ||
|
|
6ae1cb8837 | ||
|
|
217e075df8 | ||
|
|
ee6b0def55 | ||
|
|
961880b458 | ||
|
|
124dd30879 | ||
|
|
351d212777 | ||
|
|
668067e0bf | ||
|
|
463f9fd9f2 | ||
|
|
ca5d60fd69 | ||
|
|
c50a2b68bb | ||
|
|
fe68fbcd7f | ||
|
|
57ff2b2492 | ||
|
|
551ba21e9b | ||
|
|
9967c3255d | ||
|
|
2850ccb96b | ||
|
|
3bb3142b7e | ||
|
|
6e4113e92d | ||
|
|
0be850a000 | ||
|
|
e46972d7a4 | ||
|
|
b09e27b207 | ||
|
|
0788cb8a80 | ||
|
|
768ef1fad4 | ||
|
|
f6841d1e68 | ||
|
|
60a0172096 | ||
|
|
b7cd1fd662 | ||
|
|
6c267a3217 | ||
|
|
f9f7ad702c | ||
|
|
83b99db98a | ||
|
|
988357696d | ||
|
|
e929bde2bf | ||
|
|
9a64ebde0c | ||
|
|
11a259a5f0 | ||
|
|
29c65d8bff | ||
|
|
9758482360 | ||
|
|
3381e4d787 | ||
|
|
805ac7c887 | ||
|
|
ab6ba62eb1 | ||
|
|
f081d145cf | ||
|
|
4f7d3a0cb2 | ||
|
|
3cc76aae83 | ||
|
|
80c9eec6b6 | ||
|
|
7183422b17 | ||
|
|
ca2413545c | ||
|
|
4e425887e7 | ||
|
|
8b84390517 | ||
|
|
fd95bc8f5a | ||
|
|
08dc5cacd9 | ||
|
|
29ec7aaa51 | ||
|
|
820a30f0d2 | ||
|
|
f2d03736a7 | ||
|
|
f340a2eccc | ||
|
|
78c83ac1ec | ||
|
|
190bb0bd24 | ||
|
|
fdc2970d37 | ||
|
|
d55655a423 | ||
|
|
c8aafdba33 | ||
|
|
1601da4049 | ||
|
|
34d8f03af4 | ||
|
|
05996cd497 | ||
|
|
25837eeee5 | ||
|
|
c6cfbc2919 | ||
|
|
d27b47f439 | ||
|
|
9d490593d0 | ||
|
|
4bdfb7e7da | ||
|
|
2dcfc54752 | ||
|
|
504ff4ed55 | ||
|
|
e3a91ec1e3 | ||
|
|
58420b7c60 | ||
|
|
945fcbe820 | ||
|
|
02bce9b1af | ||
|
|
85910fb575 | ||
|
|
96305d9bb4 | ||
|
|
82586e293e | ||
|
|
873f7fe535 | ||
|
|
37a26591c7 | ||
|
|
ab1fb6cf8e | ||
|
|
6403642c02 | ||
|
|
de376be2ba | ||
|
|
adb750fdf4 | ||
|
|
20d051a00e | ||
|
|
7d80ea4544 | ||
|
|
eaa4f9d23b | ||
|
|
fccc881894 | ||
|
|
0119a8c78b | ||
|
|
9727956436 | ||
|
|
09175119d2 | ||
|
|
20d065d940 | ||
|
|
0c533e61bc | ||
|
|
6efdbe85e3 | ||
|
|
7b2b4b7c53 | ||
|
|
f2a8ee8ff2 | ||
|
|
7208116105 | ||
|
|
11631c0609 | ||
|
|
a00391c729 | ||
|
|
1d94aeea44 | ||
|
|
8dc3d77b59 | ||
|
|
09cd25f763 | ||
|
|
5bc8abc0ec | ||
|
|
f91f6bd78e | ||
|
|
7c9e50e37d | ||
|
|
2ea356d0be | ||
|
|
77af042413 | ||
|
|
acab405fc1 | ||
|
|
9a9a403a89 | ||
|
|
83026c7db2 | ||
|
|
06d4f569a8 | ||
|
|
b42bf48310 | ||
|
|
024194420c | ||
|
|
7c9b580681 | ||
|
|
fc2cf7ead2 | ||
|
|
df7a3a7168 | ||
|
|
2a0886b9a7 | ||
|
|
9c4b94b9e7 | ||
|
|
35e1c6c402 | ||
|
|
8cb2bfa233 | ||
|
|
0e3e349369 | ||
|
|
0102b1769b | ||
|
|
ef67339175 | ||
|
|
36600d8fa0 | ||
|
|
13d865f6f1 | ||
|
|
947a63400f | ||
|
|
f49d76b7ad | ||
|
|
1428e950bd | ||
|
|
e3b7dd7b56 | ||
|
|
9d1a071194 | ||
|
|
72c3887167 | ||
|
|
b2815b6b46 | ||
|
|
6c0e55a2a9 | ||
|
|
0560544e86 | ||
|
|
613e1a9a38 | ||
|
|
bcda3bda04 | ||
|
|
9998478c64 | ||
|
|
c368c6afe8 | ||
|
|
1614345a52 | ||
|
|
c237022831 | ||
|
|
7eb1adb532 | ||
|
|
0be3abf7b6 | ||
|
|
05fa171beb | ||
|
|
9b186c8d11 | ||
|
|
476a98ca30 | ||
|
|
925c63a96b | ||
|
|
6d83a75595 | ||
|
|
31b2397470 | ||
|
|
695c653e11 | ||
|
|
0e736666a0 | ||
|
|
1ec5a07d8e | ||
|
|
29c6610617 | ||
|
|
84a8dd994c | ||
|
|
2a0ee4ddd8 | ||
|
|
56359da691 | ||
|
|
46c37a6327 | ||
|
|
f7764cb6b2 | ||
|
|
6d802639f7 | ||
|
|
5e10baa412 | ||
|
|
72571d09ad | ||
|
|
07b09c7548 | ||
|
|
4069afeede | ||
|
|
24e2b05e61 | ||
|
|
5735ff4e03 | ||
|
|
30e097e365 | ||
|
|
b7a9d414c8 | ||
|
|
be99011f08 | ||
|
|
48eb646787 | ||
|
|
325dcdf2e5 | ||
|
|
743a02d6f6 |
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
|
||||
@@ -4,8 +4,8 @@ runs:
|
||||
steps:
|
||||
- name: Install Maven
|
||||
run: |
|
||||
wget --no-check-certificate https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz
|
||||
tar zxf apache-maven-3.9.6-bin.tar.gz
|
||||
echo "export M2_HOME=$(pwd)/apache-maven-3.9.6" >> $GITHUB_ENV
|
||||
echo "$(pwd)/apache-maven-3.9.6/bin" >> $GITHUB_PATH
|
||||
wget --no-check-certificate https://archive.apache.org/dist/maven/maven-3/3.9.11/binaries/apache-maven-3.9.11-bin.tar.gz
|
||||
tar zxf apache-maven-3.9.11-bin.tar.gz
|
||||
echo "export M2_HOME=$(pwd)/apache-maven-3.9.11" >> $GITHUB_ENV
|
||||
echo "$(pwd)/apache-maven-3.9.11/bin" >> $GITHUB_PATH
|
||||
shell: bash
|
||||
@@ -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
|
||||
@@ -11,9 +21,9 @@ runs:
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
CTEST_BIN: C:/Program Files/CMake/bin/ctest.exe
|
||||
JAVA_HOME: C:/Program Files/BellSoft/LibericaJDK-8
|
||||
SNAPPY_HOME: ${{ github.workspace }}/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: ${{ github.workspace }}/thirdparty/snappy-1.1.8;${{ github.workspace }}/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: ${{ github.workspace }}/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
SNAPPY_HOME: ${{ github.workspace }}/thirdparty/snappy-1.2.2
|
||||
SNAPPY_INCLUDE: ${{ github.workspace }}/thirdparty/snappy-1.2.2;${{ github.workspace }}/thirdparty/snappy-1.2.2/build
|
||||
SNAPPY_LIB_DEBUG: ${{ github.workspace }}/thirdparty/snappy-1.2.2/build/Debug/snappy.lib
|
||||
run: |-
|
||||
# NOTE: if ... Exit $LASTEXITCODE lines needed to exit and report failure
|
||||
echo ===================== Install Dependencies =====================
|
||||
@@ -22,14 +32,14 @@ runs:
|
||||
mkdir $Env:THIRDPARTY_HOME
|
||||
cd $Env:THIRDPARTY_HOME
|
||||
echo "Building Snappy dependency..."
|
||||
curl -Lo snappy-1.1.8.zip https://github.com/google/snappy/archive/refs/tags/1.1.8.zip
|
||||
curl -Lo snappy-1.2.2.zip https://github.com/google/snappy/archive/refs/tags/1.2.2.zip
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
unzip -q snappy-1.1.8.zip
|
||||
unzip -q snappy-1.2.2.zip
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
cd snappy-1.1.8
|
||||
cd snappy-1.2.2
|
||||
mkdir build
|
||||
cd build
|
||||
& cmake -G "$Env:CMAKE_GENERATOR" ..
|
||||
& cmake -G "$Env:CMAKE_GENERATOR" .. -DSNAPPY_BUILD_TESTS=OFF -DSNAPPY_BUILD_BENCHMARKS=OFF
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
msbuild Snappy.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
if(!$?) { Exit $LASTEXITCODE }
|
||||
@@ -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 -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
|
||||
@@ -27,24 +27,12 @@ jobs:
|
||||
git config --global --add safe.directory /__w/rocksdb/rocksdb
|
||||
tools/check_format_compatible.sh
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-run-microbench:
|
||||
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: DEBUG_LEVEL=0 make -j32 run_microbench
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-non-shm:
|
||||
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:
|
||||
TEST_TMPDIR: "/tmp/rocksdb_test_tmp"
|
||||
@@ -53,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"
|
||||
@@ -75,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
|
||||
@@ -91,15 +79,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/windows-build-steps"
|
||||
build-windows-vs2022:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/windows-build-steps"
|
||||
build-linux-arm-test-full:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
@@ -110,3 +89,44 @@ jobs:
|
||||
- run: sudo apt-get update && sudo apt-get install -y build-essential libgflags-dev
|
||||
- run: make V=1 J=4 -j4 check
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-examples:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- name: Build examples
|
||||
run: make V=1 -j4 static_lib && cd examples && make V=1 -j4
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-fuzzers:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
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-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-cmake-with-folly-lite-no-test:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: "(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"
|
||||
+106
-173
@@ -52,13 +52,21 @@ jobs:
|
||||
run: make check-buck-targets
|
||||
- name: Simple source code checks
|
||||
run: make check-sources
|
||||
- name: Sanity check check_format_compatible.sh
|
||||
run: |-
|
||||
export TEST_TMPDIR=/dev/shm/rocksdb
|
||||
rm -rf /dev/shm/rocksdb
|
||||
mkdir /dev/shm/rocksdb
|
||||
git reset --hard
|
||||
git config --global --add safe.directory /__w/rocksdb/rocksdb
|
||||
SANITY_CHECK=1 LONG_TEST=1 tools/check_format_compatible.sh
|
||||
# ========================= Linux With Tests ======================== #
|
||||
build-linux:
|
||||
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
|
||||
@@ -70,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
|
||||
@@ -89,49 +97,35 @@ 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-cmake-with-folly-lite-no-test:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
env:
|
||||
CC: gcc-10
|
||||
CXX: g++-10
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- uses: "./.github/actions/setup-folly"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY_LITE=1 -DWITH_GFLAGS=1 .. && make V=1 -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-make-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: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:
|
||||
@@ -139,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:
|
||||
@@ -172,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
|
||||
@@ -184,13 +176,13 @@ 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
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- run: ENCRYPTED_ENV=1 ROCKSDB_DISABLE_SNAPPY=1 ROCKSDB_DISABLE_ZLIB=1 ROCKSDB_DISABLE_BZIP=1 ROCKSDB_DISABLE_LZ4=1 ROCKSDB_DISABLE_ZSTD=1 make V=1 J=32 -j32 check
|
||||
- run: "./sst_dump --help | grep -E -q 'Supported compression types: kNoCompression$' # Verify no compiled in compression\n"
|
||||
- run: "./sst_dump --help | grep -E -q 'Supported built-in compression types: kNoCompression$' # Verify no compiled in compression\n"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
# ======================== Linux No Test Runs ======================= #
|
||||
build-linux-release:
|
||||
@@ -198,141 +190,82 @@ 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
|
||||
- run: make V=1 -j32 LIB_MODE=shared release
|
||||
- run: ls librocksdb.so
|
||||
- run: "./db_stress --version"
|
||||
- run: "./trace_analyzer --version" # A tool dependent on gflags that can run in release build
|
||||
- run: make clean
|
||||
- run: make V=1 -j32 release
|
||||
- run: USE_RTTI=1 make V=1 -j32 release
|
||||
- run: ls librocksdb.a
|
||||
- run: "./db_stress --version"
|
||||
- run: "./trace_analyzer --version"
|
||||
- run: make clean
|
||||
- run: apt-get remove -y libgflags-dev
|
||||
- run: make V=1 -j32 LIB_MODE=shared release
|
||||
- run: ls librocksdb.so
|
||||
- run: if ./db_stress --version; then false; else true; fi
|
||||
- run: if ./trace_analyzer --version; then false; else true; fi
|
||||
- run: make clean
|
||||
- run: make V=1 -j32 release
|
||||
- run: USE_RTTI=1 make V=1 -j32 release
|
||||
- run: ls librocksdb.a
|
||||
- run: if ./db_stress --version; then false; else true; fi
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-release-rtti:
|
||||
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: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j16 static_lib tools db_bench
|
||||
- run: "./db_stress --version"
|
||||
- run: make clean
|
||||
- run: apt-get remove -y libgflags-dev
|
||||
- run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j16 static_lib tools db_bench
|
||||
- run: if ./db_stress --version; then false; else true; fi
|
||||
build-examples:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- name: Build examples
|
||||
run: make V=1 -j4 static_lib && cd examples && make V=1 -j4
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-fuzzers:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/pre-steps"
|
||||
- name: Build rocksdb lib
|
||||
run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 make -j4 static_lib
|
||||
- name: Build fuzzers
|
||||
run: cd fuzz && make sst_file_writer_fuzzer db_fuzzer db_map_fuzzer
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang-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
|
||||
- run: if ./trace_analyzer --version; then false; else true; fi
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-clang-13-no_test_run:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
labels: 8-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
|
||||
# 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
|
||||
- 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
|
||||
- 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
|
||||
@@ -360,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
|
||||
@@ -368,104 +301,98 @@ 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:
|
||||
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 CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 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-clang10-ubsan:
|
||||
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: COMPILE_WITH_UBSAN=1 CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 USE_CLANG=1 make V=1 -j32 ubsan_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-vs2019:
|
||||
build-windows-vs2022:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-8-core
|
||||
env:
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -476,11 +403,13 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:centos6_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 }}
|
||||
@@ -497,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:centos6_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 }}
|
||||
@@ -525,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
|
||||
@@ -537,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"
|
||||
@@ -552,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"
|
||||
@@ -574,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"
|
||||
@@ -599,7 +532,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:rockylinux8_x64-be
|
||||
image: evolvedbinary/rocksjava:alpine3_x64-be
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
|
||||
@@ -88,6 +88,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/memtable_list.cc",
|
||||
"db/merge_helper.cc",
|
||||
"db/merge_operator.cc",
|
||||
"db/multi_scan.cc",
|
||||
"db/output_validator.cc",
|
||||
"db/periodic_task_scheduler.cc",
|
||||
"db/range_del_aggregator.cc",
|
||||
@@ -113,6 +114,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/write_controller.cc",
|
||||
"db/write_stall_stats.cc",
|
||||
"db/write_thread.cc",
|
||||
"db_stress_tool/db_stress_compression_manager.cc",
|
||||
"env/composite_env.cc",
|
||||
"env/env.cc",
|
||||
"env/env_chroot.cc",
|
||||
@@ -249,6 +251,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"trace_replay/trace_record_result.cc",
|
||||
"trace_replay/trace_replay.cc",
|
||||
"util/async_file_reader.cc",
|
||||
"util/auto_tune_compressor.cc",
|
||||
"util/build_version.cc",
|
||||
"util/cleanable.cc",
|
||||
"util/coding.cc",
|
||||
@@ -267,6 +270,7 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
"util/ribbon_config.cc",
|
||||
"util/simple_mixed_compressor.cc",
|
||||
"util/slice.cc",
|
||||
"util/status.cc",
|
||||
"util/stderr_logger.cc",
|
||||
@@ -415,16 +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",
|
||||
@@ -446,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)
|
||||
@@ -4709,6 +4718,12 @@ cpp_unittest_wrapper(name="compressed_secondary_cache_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="compression_test",
|
||||
srcs=["util/compression_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="configurable_test",
|
||||
srcs=["options/configurable_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
@@ -4829,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"],
|
||||
@@ -5179,12 +5200,24 @@ 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"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="interval_test",
|
||||
srcs=["util/interval_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="io_posix_test",
|
||||
srcs=["env/io_posix_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
@@ -5365,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"],
|
||||
|
||||
+44
-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)
|
||||
@@ -721,6 +734,7 @@ set(SOURCES
|
||||
db/memtable_list.cc
|
||||
db/merge_helper.cc
|
||||
db/merge_operator.cc
|
||||
db/multi_scan.cc
|
||||
db/output_validator.cc
|
||||
db/periodic_task_scheduler.cc
|
||||
db/range_del_aggregator.cc
|
||||
@@ -746,6 +760,7 @@ set(SOURCES
|
||||
db/write_controller.cc
|
||||
db/write_stall_stats.cc
|
||||
db/write_thread.cc
|
||||
db_stress_tool/db_stress_compression_manager.cc
|
||||
env/composite_env.cc
|
||||
env/env.cc
|
||||
env/env_chroot.cc
|
||||
@@ -874,11 +889,13 @@ set(SOURCES
|
||||
trace_replay/trace_record.cc
|
||||
trace_replay/trace_replay.cc
|
||||
util/async_file_reader.cc
|
||||
util/auto_tune_compressor.cc
|
||||
util/cleanable.cc
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
util/comparator.cc
|
||||
util/compression.cc
|
||||
util/simple_mixed_compressor.cc
|
||||
util/compression_context_cache.cc
|
||||
util/concurrent_task_limiter_impl.cc
|
||||
util/crc32c.cc
|
||||
@@ -1342,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
|
||||
@@ -1435,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
|
||||
@@ -1456,6 +1475,7 @@ if(WITH_TESTS)
|
||||
util/autovector_test.cc
|
||||
util/bloom_test.cc
|
||||
util/coding_test.cc
|
||||
util/compression_test.cc
|
||||
util/crc32c_test.cc
|
||||
util/defer_test.cc
|
||||
util/dynamic_bloom_test.cc
|
||||
@@ -1499,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
|
||||
@@ -1609,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>
|
||||
+167
-5
@@ -1,6 +1,168 @@
|
||||
# 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 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.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in BackupEngine that can crash backup due to a null FSWritableFile passed to WritableFileWriter.
|
||||
* Fix DB::NewMultiScan iterator to respect the scan upper bound specified in ScanOptions
|
||||
|
||||
### Performance Improvements
|
||||
* Optimized MultiScan using BlockBasedTable to coalesce I/Os and prefetch all data blocks.
|
||||
|
||||
## 10.4.0 (06/20/2025)
|
||||
### New Features
|
||||
* Add a new CF option `memtable_avg_op_scan_flush_trigger` that supports triggering memtable flush when an iterator scans through an expensive range of keys, with the average number of skipped keys from the active memtable exceeding the threshold.
|
||||
* Vector based memtable now supports concurrent writers (DBOptions::allow_concurrent_memtable_write) #13675.
|
||||
* Add new experimental `TransactionOptions::large_txn_commit_optimize_byte_threshold` to enable optimizations for large transaction commit by transaction batch data size.
|
||||
* Add a new option `CompactionOptionsUniversal::reduce_file_locking` and if it's true, auto universal compaction picking will adjust to minimize locking of input files when bottom priority compactions are waiting to run. This can increase the likelihood of existing L0s being selected for compaction, thereby improving write stall and reducing read regression.
|
||||
* Add new `format_version=7` to aid experimental support of custom compression algorithms with CompressionManager and block-based table. This format version includes changing the format of `TableProperties::compression_name`.
|
||||
|
||||
### Public API Changes
|
||||
* Change NewExternalTableFactory to return a unique_ptr instead of shared_ptr.
|
||||
* Add an optional min file size requirement for deletion triggered compaction. It can be specified when creating `CompactOnDeletionCollectorFactory`.
|
||||
|
||||
### Behavior Changes
|
||||
* `TransactionOptions::large_txn_commit_optimize_threshold` now has default value 0 for disabled. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` now has no effect on transactions.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where CreateColumnFamilyWithImport() could miss the SST file for the memtable flush it triggered. The exported CF then may not contain the updates in the memtable when CreateColumnFamilyWithImport() is called.
|
||||
* Fix iterator operations returning NotImplemented status if disallow_memtable_writes and paranoid_memory_checks CF options are both set.
|
||||
* Fixed handling of file checksums in IngestExternalFile() to allow providing checksums using recognized but not necessarily the DB's preferred checksum function, to ease migration between checksum functions.
|
||||
|
||||
## 10.3.0 (05/17/2025)
|
||||
### New Features
|
||||
* Add new experimental `CompactionOptionsFIFO::allow_trivial_copy_when_change_temperature` along with `CompactionOptionsFIFO::trivial_copy_buffer_size` to allow optimizing FIFO compactions with tiering when kChangeTemperature to move files from source tier FileSystem to another tier FileSystem via trivial and direct copying raw sst file instead of reading thru the content of the SST file then rebuilding the table files.
|
||||
* Add a new field to Compaction Stats in LOG files for the pre-compression size written to each level.
|
||||
* Add new experimental `TransactionOptions::large_txn_commit_optimize_threshold` to enable optimizations for large transaction commit with per transaction threshold. `TransactionDBOptions::txn_commit_bypass_memtable_threshold` is deprecated in favor of this transaction option.
|
||||
* [internal team use only] Allow an application-defined `request_id` to be passed to RocksDB and propagated to the filesystem via IODebugContext
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug where transaction lock upgrade can incorrectly fail with a Deadlock status. This happens when a transaction has a non-zero timeout and tries to upgrade a shared lock that is also held by another transaction.
|
||||
* Pass wrapped WritableFileWriter pointer to ExternalTableBuilder so that the file checksum can be correctly calculated and returned by SstFileWriter for external table files.
|
||||
* Fix an infinite-loop bug in transaction locking. This can happen if a transaction reaches lock limit and its time out expires before it attempts to wait for it.
|
||||
* Fixed a potential data race with `CompressionOptions::parallel_threads > 1` and a `TablePropertiesCollector` overriding `BlockAdd()`.
|
||||
|
||||
## 10.2.0 (04/21/2025)
|
||||
### New Features
|
||||
* Provide histogram stats `COMPACTION_PREFETCH_BYTES` to measure number of bytes for RocksDB's prefetching (as opposed to file
|
||||
system's prefetch) on SST file during compaction read
|
||||
* A new API DB::GetNewestUserDefinedTimestamp is added to return the newest user defined timestamp seen in a column family
|
||||
* Introduce API `IngestWriteBatchWithIndex()` for ingesting updates into DB while bypassing memtable writes. This improves performance when writing a large write batch to the DB.
|
||||
* Add a new CF option `memtable_op_scan_flush_trigger` that triggers a flush of the memtable if an iterator's Seek()/Next() scans over a certain number of invisible entries from the memtable.
|
||||
|
||||
### Public API Changes
|
||||
* AdvancedColumnFamilyOptions.max_write_buffer_number_to_maintain is deleted. It's deprecated since introduction of a better option max_write_buffer_size_to_maintain since RocksDB 6.5.0.
|
||||
* Deprecated API `DB::MaxMemCompactionLevel()`.
|
||||
* Deprecated `ReadOptions::ignore_range_deletions`.
|
||||
* Deprecated API `experimental::PromoteL0()`.
|
||||
* Added arbitrary string map for additional options to be overridden for remote compactions
|
||||
* 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
|
||||
* Make stats `PREFETCH_BYTES_USEFUL`, `PREFETCH_HITS`, `PREFETCH_BYTES` only account for prefetching during user initiated scan
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in Posix file system that the FSWritableFile created via `FileSystem::ReopenWritableFile` internally does not track the correct file size.
|
||||
* Fix a bug where tail size of remote compaction output is not persisted in primary db's manifest
|
||||
|
||||
## 10.1.0 (03/24/2025)
|
||||
### New Features
|
||||
* Added a new `DBOptions.calculate_sst_write_lifetime_hint_set` setting that allows to customize which compaction styles SST write lifetime hint calculation is allowed on. Today RocksDB supports only two modes `kCompactionStyleLevel` and `kCompactionStyleUniversal`.
|
||||
@@ -141,7 +303,7 @@
|
||||
* 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
|
||||
@@ -337,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)
|
||||
@@ -364,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.
|
||||
@@ -429,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.
|
||||
@@ -1217,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)
|
||||
|
||||
@@ -1357,6 +1359,9 @@ filter_bench: $(OBJ_DIR)/util/filter_bench.o $(LIBRARY)
|
||||
db_stress: $(OBJ_DIR)/db_stress_tool/db_stress.o $(STRESS_LIBRARY) $(TOOLS_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_stress_compression_manager: $(OBJ_DIR)/db_stress_tool/db_stress_compression_manager.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
write_stress: $(OBJ_DIR)/tools/write_stress.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1422,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)
|
||||
@@ -1491,6 +1496,12 @@ 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)
|
||||
|
||||
db_logical_block_size_cache_test: $(OBJ_DIR)/db/db_logical_block_size_cache_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1731,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)
|
||||
|
||||
@@ -1875,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)
|
||||
|
||||
@@ -2034,6 +2051,9 @@ wide_column_serialization_test: $(OBJ_DIR)/db/wide/wide_column_serialization_tes
|
||||
wide_columns_helper_test: $(OBJ_DIR)/db/wide/wide_columns_helper_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
interval_test: $(OBJ_DIR)/util/interval_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
@@ -2144,14 +2164,14 @@ ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
BZIP2_DOWNLOAD_BASE ?= http://sourceware.org/pub/bzip2
|
||||
SNAPPY_VER ?= 1.2.1
|
||||
SNAPPY_SHA256 ?= 736aeb64d86566d2236ddffa2865ee5d7a82d26c9016b36218fcc27ea4f09f86
|
||||
SNAPPY_VER ?= 1.2.2
|
||||
SNAPPY_SHA256 ?= 90f74bc1fbf78a6c56b3c4a082a05103b3a56bb17bca1a27e052ea11723292dc
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.9.4
|
||||
LZ4_SHA256 ?= 0b0e3aa07c8c063ddf40b082bdf7e37a1562bda40a0ff5272957f3e987e0e54b
|
||||
LZ4_VER ?= 1.10.0
|
||||
LZ4_SHA256 ?= 537512904744b35e232912055ccf8ec66d768639ff3abe5788d90d792ec5f48b
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.5.5
|
||||
ZSTD_SHA256 ?= 98e9c3d949d1b924e28e01eccb7deed865eefebf25c2f21c702e5cd5b63b85e1
|
||||
ZSTD_VER ?= 1.5.7
|
||||
ZSTD_SHA256 ?= 37d7284556b20954e56e1ca85b80226768902e2edabd3b649e9e72c0c9012ee3
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
@@ -2242,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:
|
||||
@@ -2372,27 +2392,27 @@ rocksdbjavastaticreleasedocker: rocksdbjavastaticosx rocksdbjavastaticdockerx86
|
||||
|
||||
rocksdbjavastaticdockerx86:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-be --platform linux/386 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_x86-be --platform linux/386 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:centos7_x86-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerx86_64:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x64-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:centos6_x64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_x64-be --platform linux/amd64 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:centos7_x64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerppc64le:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_ppc64le-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_ppc64le-be --platform linux/ppc64le --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerarm64v8:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_arm64v8-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:centos7_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_arm64v8-be --platform linux/aarch64 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:centos7_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockers390x:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_s390x-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:ubuntu18_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_s390x-be --platform linux/s390x --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:ubuntu18_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerriscv64:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_riscv64-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:ubuntu20_riscv64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_riscv64-be --platform linux/riscv64 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:ubuntu20_riscv64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerx86musl:
|
||||
mkdir -p java/target
|
||||
@@ -2400,19 +2420,19 @@ rocksdbjavastaticdockerx86musl:
|
||||
|
||||
rocksdbjavastaticdockerx86_64musl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x64-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:alpine3_x64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_x64-musl-be --platform linux/amd64 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:alpine3_x64-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerppc64lemusl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_ppc64le-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:alpine3_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_ppc64le-musl-be --platform linux/ppc64le --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:alpine3_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockerarm64v8musl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_arm64v8-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:alpine3_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_arm64v8-musl-be --platform linux/aarch64 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:alpine3_arm64v8-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticdockers390xmusl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_s390x-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:alpine3_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
docker run --rm --name rocksdb_linux_s390x-musl-be --platform linux/s390x --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) --env J=$(J) evolvedbinary/rocksjava:alpine3_s390x-be /rocksdb-host/java/crossbuild/docker-build-linux.sh
|
||||
|
||||
rocksdbjavastaticpublish: rocksdbjavastaticrelease rocksdbjavastaticpublishcentral
|
||||
|
||||
@@ -2467,8 +2487,8 @@ jtest_run:
|
||||
jtest: rocksdbjava
|
||||
cd java;$(MAKE) sample test
|
||||
|
||||
jpmd: rocksdbjava rocksdbjavageneratepom
|
||||
cd java;$(MAKE) pmd
|
||||
jpmd: rocksdbjavageneratepom
|
||||
cd java;$(MAKE) java java_test pmd
|
||||
|
||||
jdb_bench:
|
||||
cd java;$(MAKE) db_bench;
|
||||
@@ -2478,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.
|
||||
@@ -2489,11 +2511,13 @@ 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 78286282478e1ae05b2e8cbcf0e2139eab283bea
|
||||
@# NOTE: this hack is required for clang in some cases
|
||||
perl -pi -e 's/int rv = syscall/int rv = (int)syscall/' third-party/folly/folly/detail/Futex.cpp
|
||||
@# NOTE: this hack is required for gcc in some cases
|
||||
perl -pi -e 's/(__has_include.<experimental.memory_resource>.)/__cpp_rtti && $$1/' third-party/folly/folly/memory/MemoryResource.h
|
||||
cd third-party/folly && git reset --hard $(FOLLY_COMMIT_HASH)
|
||||
@# Apparently missing include
|
||||
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
|
||||
@# Warning-as-error on memcpy
|
||||
perl -pi -e 's/memcpy.&ptr/memcpy((void*)&ptr/' third-party/folly/folly/lang/Exception.cpp
|
||||
@# const mismatch
|
||||
perl -pi -e 's/: environ/: (const char**)(environ)/' third-party/folly/folly/Subprocess.cpp
|
||||
@# NOTE: boost source will be needed for any build including `USE_FOLLY_LITE` builds as those depend on boost headers
|
||||
cd third-party/folly && $(PYTHON) build/fbcode_builder/getdeps.py fetch boost
|
||||
|
||||
|
||||
@@ -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,18 +45,21 @@ 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
|
||||
COMMON_FLAGS="-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX"
|
||||
|
||||
# Default to fbcode gcc on internal fb machines
|
||||
if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
# Default to fbcode gcc on Meta internal machines
|
||||
IS_META_HOST="$(hostname | grep -E '(facebook|meta).com|fbinfra.net')"
|
||||
if [ -z "$ROCKSDB_NO_FBCODE" -a "$IS_META_HOST" ]; then
|
||||
if [ -d /mnt/gvfs/third-party ]; then
|
||||
echo "NOTE: Using fbcode build" >&2
|
||||
FBCODE_BUILD="true"
|
||||
# If we're compiling with TSAN or shared lib, we need pic build
|
||||
PIC_BUILD=$COMPILE_WITH_TSAN
|
||||
@@ -64,6 +67,11 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
PIC_BUILD=1
|
||||
fi
|
||||
source "$PWD/build_tools/fbcode_config_platform010.sh"
|
||||
else
|
||||
echo "************************************************************************" >&2
|
||||
echo "WARNING: -d /mnt/gvfs/third-party failed; no fbcode build" >&2
|
||||
echo "************************************************************************" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Delete existing output, if it exists
|
||||
@@ -71,7 +79,9 @@ rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
if test -z "$CC"; then
|
||||
if [ -x "$(command -v cc)" ]; then
|
||||
if [ "$USE_CLANG" -a -x "$(command -v clang)" ]; then
|
||||
CC=clang
|
||||
elif [ -x "$(command -v cc)" ]; then
|
||||
CC=cc
|
||||
elif [ -x "$(command -v clang)" ]; then
|
||||
CC=clang
|
||||
@@ -81,7 +91,9 @@ if test -z "$CC"; then
|
||||
fi
|
||||
|
||||
if test -z "$CXX"; then
|
||||
if [ -x "$(command -v g++)" ]; then
|
||||
if [ "$USE_CLANG" -a -x "$(command -v clang++)" ]; then
|
||||
CXX=clang++
|
||||
elif [ -x "$(command -v g++)" ]; then
|
||||
CXX=g++
|
||||
elif [ -x "$(command -v clang++)" ]; then
|
||||
CXX=clang++
|
||||
@@ -91,7 +103,9 @@ if test -z "$CXX"; then
|
||||
fi
|
||||
|
||||
if test -z "$AR"; then
|
||||
if [ -x "$(command -v gcc-ar)" ]; then
|
||||
if [ "$USE_CLANG" -a -x "$(command -v llvm-ar)" ]; then
|
||||
AR=llvm-ar
|
||||
elif [ -x "$(command -v gcc-ar)" ]; then
|
||||
AR=gcc-ar
|
||||
elif [ -x "$(command -v llvm-ar)" ]; then
|
||||
AR=llvm-ar
|
||||
|
||||
@@ -118,6 +118,9 @@ fi
|
||||
# fi
|
||||
set -e
|
||||
|
||||
# Exclude third-party from formatting
|
||||
EXCLUDE=':!third-party/'
|
||||
|
||||
uncommitted_code=`git diff HEAD`
|
||||
|
||||
# If there's no uncommitted changes, we assume user are doing post-commit
|
||||
@@ -137,11 +140,11 @@ then
|
||||
# should be relevant for formatting fixes.
|
||||
FORMAT_UPSTREAM_MERGE_BASE="$(git merge-base "$FORMAT_UPSTREAM" HEAD)"
|
||||
# Get the differences
|
||||
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -p 1) || true
|
||||
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
|
||||
echo "Checking format of changes not yet in $FORMAT_UPSTREAM..."
|
||||
else
|
||||
# Check the format of uncommitted lines,
|
||||
diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1) || true
|
||||
diffs=$(git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -p 1) || true
|
||||
echo "Checking format of uncommitted changes..."
|
||||
fi
|
||||
|
||||
@@ -187,9 +190,9 @@ fi
|
||||
# Do in-place format adjustment.
|
||||
if [ -z "$uncommitted_code" ]
|
||||
then
|
||||
git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -i -p 1
|
||||
git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
|
||||
else
|
||||
git diff -U0 HEAD | $CLANG_FORMAT_DIFF -i -p 1
|
||||
git diff -U0 HEAD -- $EXCLUDE | $CLANG_FORMAT_DIFF -i -p 1
|
||||
fi
|
||||
echo "Files reformatted!"
|
||||
|
||||
|
||||
@@ -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
+90
-13
@@ -60,6 +60,8 @@ DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
DEFINE_uint32(value_bytes_estimate, 0,
|
||||
"If > 0, overrides estimated_entry_charge or "
|
||||
"min_avg_entry_charge depending on cache_type.");
|
||||
DEFINE_double(compressible_to_ratio, 0.5,
|
||||
"Approximate size ratio that values can be compressed to.");
|
||||
|
||||
DEFINE_int32(
|
||||
degenerate_hash_bits, 0,
|
||||
@@ -182,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 {
|
||||
|
||||
@@ -291,10 +298,19 @@ struct KeyGen {
|
||||
|
||||
Cache::ObjectPtr createValue(Random64& rnd, MemoryAllocator* alloc) {
|
||||
char* rv = AllocateBlock(FLAGS_value_bytes, alloc).release();
|
||||
// Fill with some filler data, and take some CPU time
|
||||
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
|
||||
// Fill with some filler data, and take some CPU time, but add redundancy
|
||||
// as requested for compressibility.
|
||||
uint32_t random_fill_size = std::max(
|
||||
uint32_t{1}, std::min(FLAGS_value_bytes,
|
||||
static_cast<uint32_t>(FLAGS_compressible_to_ratio *
|
||||
FLAGS_value_bytes)));
|
||||
uint32_t i = 0;
|
||||
for (; i < random_fill_size; i += 8) {
|
||||
EncodeFixed64(rv + i, rnd.Next());
|
||||
}
|
||||
for (; i < FLAGS_value_bytes; i++) {
|
||||
rv[i] = rv[i % random_fill_size];
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -309,16 +325,16 @@ Status SaveToFn(Cache::ObjectPtr from_obj, size_t /*from_offset*/,
|
||||
|
||||
Status CreateFn(const Slice& data, CompressionType /*type*/,
|
||||
CacheTier /*source*/, Cache::CreateContext* /*context*/,
|
||||
MemoryAllocator* /*allocator*/, Cache::ObjectPtr* out_obj,
|
||||
MemoryAllocator* alloc, Cache::ObjectPtr* out_obj,
|
||||
size_t* out_charge) {
|
||||
*out_obj = new char[data.size()];
|
||||
*out_obj = AllocateBlock(data.size(), alloc).release();
|
||||
memcpy(*out_obj, data.data(), data.size());
|
||||
*out_charge = data.size();
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
void DeleteFn(Cache::ObjectPtr value, MemoryAllocator* alloc) {
|
||||
CustomDeleter{alloc}(static_cast<char*>(value));
|
||||
CacheAllocationDeleter{alloc}(static_cast<char*>(value));
|
||||
}
|
||||
|
||||
Cache::CacheItemHelper helper1_wos(CacheEntryRole::kDataBlock, DeleteFn);
|
||||
@@ -376,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;
|
||||
@@ -395,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;
|
||||
}
|
||||
@@ -409,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 */,
|
||||
@@ -417,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;
|
||||
@@ -479,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();
|
||||
@@ -1141,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);
|
||||
@@ -1151,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
+192
-152
@@ -16,6 +16,31 @@
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
// Format of values in CompressedSecondaryCache:
|
||||
// If enable_custom_split_merge:
|
||||
// * A chain of CacheValueChunk representing the sequence of bytes for a tagged
|
||||
// value. The overall length of the tagged value is determined by the chain
|
||||
// of CacheValueChunks.
|
||||
// If !enable_custom_split_merge:
|
||||
// * A LengthPrefixedSlice (starts with varint64 size) of a tagged value.
|
||||
//
|
||||
// A tagged value has a 2-byte header before the "saved" or compressed block
|
||||
// data:
|
||||
// * 1 byte for "source" CacheTier indicating which tier is responsible for
|
||||
// compression/decompression.
|
||||
// * 1 byte for compression type which is generated/used by
|
||||
// CompressedSecondaryCache iff source == CacheTier::kVolatileCompressedTier
|
||||
// (original entry passed in was uncompressed). Otherwise, the compression
|
||||
// type is preserved from the entry passed in.
|
||||
constexpr uint32_t kTagSize = 2;
|
||||
|
||||
// Size of tag + varint size prefix when applicable
|
||||
uint32_t GetHeaderSize(size_t data_size, bool enable_split_merge) {
|
||||
return (enable_split_merge ? 0 : VarintLength(kTagSize + data_size)) +
|
||||
kTagSize;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
CompressedSecondaryCache::CompressedSecondaryCache(
|
||||
const CompressedSecondaryCacheOptions& opts)
|
||||
@@ -24,7 +49,14 @@ CompressedSecondaryCache::CompressedSecondaryCache(
|
||||
cache_res_mgr_(std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache_))),
|
||||
disable_cache_(opts.capacity == 0) {}
|
||||
disable_cache_(opts.capacity == 0) {
|
||||
auto mgr =
|
||||
GetBuiltinCompressionManager(cache_options_.compress_format_version);
|
||||
compressor_ = mgr->GetCompressor(cache_options_.compression_opts,
|
||||
cache_options_.compression_type);
|
||||
decompressor_ =
|
||||
mgr->GetDecompressorOptimizeFor(cache_options_.compression_type);
|
||||
}
|
||||
|
||||
CompressedSecondaryCache::~CompressedSecondaryCache() = default;
|
||||
|
||||
@@ -33,13 +65,9 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
Statistics* stats, bool& kept_in_sec_cache) {
|
||||
assert(helper);
|
||||
// This is a minor optimization. Its ok to skip it in TSAN in order to
|
||||
// avoid a false positive.
|
||||
#ifndef __SANITIZE_THREAD__
|
||||
if (disable_cache_) {
|
||||
if (disable_cache_.LoadRelaxed()) {
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle;
|
||||
kept_in_sec_cache = false;
|
||||
@@ -55,75 +83,58 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CacheAllocationPtr* ptr{nullptr};
|
||||
CacheAllocationPtr merged_value;
|
||||
size_t handle_value_charge{0};
|
||||
const char* data_ptr = nullptr;
|
||||
CacheTier source = CacheTier::kVolatileCompressedTier;
|
||||
CompressionType type = cache_options_.compression_type;
|
||||
std::string merged_value;
|
||||
Slice tagged_data;
|
||||
if (cache_options_.enable_custom_split_merge) {
|
||||
CacheValueChunk* value_chunk_ptr =
|
||||
reinterpret_cast<CacheValueChunk*>(handle_value);
|
||||
merged_value = MergeChunksIntoValue(value_chunk_ptr, handle_value_charge);
|
||||
ptr = &merged_value;
|
||||
data_ptr = ptr->get();
|
||||
static_cast<CacheValueChunk*>(handle_value);
|
||||
merged_value = MergeChunksIntoValue(value_chunk_ptr);
|
||||
tagged_data = Slice(merged_value);
|
||||
} else {
|
||||
uint32_t type_32 = static_cast<uint32_t>(type);
|
||||
uint32_t source_32 = static_cast<uint32_t>(source);
|
||||
ptr = reinterpret_cast<CacheAllocationPtr*>(handle_value);
|
||||
handle_value_charge = cache_->GetCharge(lru_handle);
|
||||
data_ptr = ptr->get();
|
||||
data_ptr = GetVarint32Ptr(data_ptr, data_ptr + 1,
|
||||
static_cast<uint32_t*>(&type_32));
|
||||
type = static_cast<CompressionType>(type_32);
|
||||
data_ptr = GetVarint32Ptr(data_ptr, data_ptr + 1,
|
||||
static_cast<uint32_t*>(&source_32));
|
||||
source = static_cast<CacheTier>(source_32);
|
||||
uint64_t data_size = 0;
|
||||
data_ptr = GetVarint64Ptr(data_ptr, ptr->get() + handle_value_charge,
|
||||
static_cast<uint64_t*>(&data_size));
|
||||
assert(handle_value_charge > data_size);
|
||||
handle_value_charge = data_size;
|
||||
tagged_data = GetLengthPrefixedSlice(static_cast<char*>(handle_value));
|
||||
}
|
||||
MemoryAllocator* allocator = cache_options_.memory_allocator.get();
|
||||
|
||||
Status s;
|
||||
Cache::ObjectPtr value{nullptr};
|
||||
size_t charge{0};
|
||||
auto source = lossless_cast<CacheTier>(tagged_data[0]);
|
||||
auto type = lossless_cast<CompressionType>(tagged_data[1]);
|
||||
|
||||
std::unique_ptr<char[]> uncompressed;
|
||||
Slice saved(tagged_data.data() + kTagSize, tagged_data.size() - kTagSize);
|
||||
if (source == CacheTier::kVolatileCompressedTier) {
|
||||
if (cache_options_.compression_type == kNoCompression ||
|
||||
cache_options_.do_not_compress_roles.Contains(helper->role)) {
|
||||
s = helper->create_cb(Slice(data_ptr, handle_value_charge),
|
||||
kNoCompression, CacheTier::kVolatileTier,
|
||||
create_context, allocator, &value, &charge);
|
||||
} else {
|
||||
UncompressionContext uncompression_context(
|
||||
cache_options_.compression_type);
|
||||
UncompressionInfo uncompression_info(uncompression_context,
|
||||
UncompressionDict::GetEmptyDict(),
|
||||
cache_options_.compression_type);
|
||||
|
||||
size_t uncompressed_size{0};
|
||||
CacheAllocationPtr uncompressed =
|
||||
UncompressData(uncompression_info, (char*)data_ptr,
|
||||
handle_value_charge, &uncompressed_size,
|
||||
cache_options_.compress_format_version, allocator);
|
||||
|
||||
if (!uncompressed) {
|
||||
if (type != kNoCompression) {
|
||||
// TODO: can we do something to avoid yet another allocation?
|
||||
Decompressor::Args args;
|
||||
args.compressed_data = saved;
|
||||
args.compression_type = type;
|
||||
Status s = decompressor_->ExtractUncompressedSize(args);
|
||||
assert(s.ok()); // in-memory data
|
||||
if (s.ok()) {
|
||||
uncompressed = std::make_unique<char[]>(args.uncompressed_size);
|
||||
s = decompressor_->DecompressBlock(args, uncompressed.get());
|
||||
assert(s.ok()); // in-memory data
|
||||
}
|
||||
if (!s.ok()) {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
|
||||
return nullptr;
|
||||
}
|
||||
s = helper->create_cb(Slice(uncompressed.get(), uncompressed_size),
|
||||
kNoCompression, CacheTier::kVolatileTier,
|
||||
create_context, allocator, &value, &charge);
|
||||
saved = Slice(uncompressed.get(), args.uncompressed_size);
|
||||
type = kNoCompression;
|
||||
// Free temporary compressed data as early as we can. This could matter
|
||||
// for unusually large blocks because we also have
|
||||
// * Another compressed copy above (from lru_cache).
|
||||
// * The uncompressed copy in `uncompressed`.
|
||||
// * Another uncompressed copy in `result_value` below.
|
||||
// Let's try to max out at 3 copies instead of 4.
|
||||
merged_value = std::string();
|
||||
}
|
||||
} else {
|
||||
// The item was not compressed by us. Let the helper create_cb
|
||||
// uncompress it
|
||||
s = helper->create_cb(Slice(data_ptr, handle_value_charge), type, source,
|
||||
create_context, allocator, &value, &charge);
|
||||
// Reduced as if it came from primary cache
|
||||
source = CacheTier::kVolatileTier;
|
||||
}
|
||||
|
||||
Cache::ObjectPtr result_value = nullptr;
|
||||
size_t result_charge = 0;
|
||||
Status s = helper->create_cb(saved, type, source, create_context,
|
||||
cache_options_.memory_allocator.get(),
|
||||
&result_value, &result_charge);
|
||||
if (!s.ok()) {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
|
||||
return nullptr;
|
||||
@@ -141,7 +152,8 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
kept_in_sec_cache = true;
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
handle.reset(new CompressedSecondaryCacheResultHandle(value, charge));
|
||||
handle.reset(
|
||||
new CompressedSecondaryCacheResultHandle(result_value, result_charge));
|
||||
RecordTick(stats, COMPRESSED_SECONDARY_CACHE_HITS);
|
||||
return handle;
|
||||
}
|
||||
@@ -164,88 +176,111 @@ bool CompressedSecondaryCache::MaybeInsertDummy(const Slice& key) {
|
||||
|
||||
Status CompressedSecondaryCache::InsertInternal(
|
||||
const Slice& key, Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper, CompressionType type,
|
||||
const Cache::CacheItemHelper* helper, CompressionType from_type,
|
||||
CacheTier source) {
|
||||
if (source != CacheTier::kVolatileCompressedTier &&
|
||||
cache_options_.enable_custom_split_merge) {
|
||||
// We don't support custom split/merge for the tiered case
|
||||
return Status::OK();
|
||||
}
|
||||
bool enable_split_merge = cache_options_.enable_custom_split_merge;
|
||||
const Cache::CacheItemHelper* internal_helper = GetHelper(enable_split_merge);
|
||||
|
||||
auto internal_helper = GetHelper(cache_options_.enable_custom_split_merge);
|
||||
char header[20];
|
||||
char* payload = header;
|
||||
payload = EncodeVarint32(payload, static_cast<uint32_t>(type));
|
||||
payload = EncodeVarint32(payload, static_cast<uint32_t>(source));
|
||||
size_t data_size = (*helper->size_cb)(value);
|
||||
char* data_size_ptr = payload;
|
||||
payload = EncodeVarint64(payload, data_size);
|
||||
// TODO: variant of size_cb that also returns a pointer to the data if
|
||||
// already available. Saves an allocation if we keep the compressed version.
|
||||
const size_t data_size_original = (*helper->size_cb)(value);
|
||||
|
||||
size_t header_size = payload - header;
|
||||
size_t total_size = data_size + header_size;
|
||||
CacheAllocationPtr ptr =
|
||||
AllocateBlock(total_size, cache_options_.memory_allocator.get());
|
||||
char* data_ptr = ptr.get() + header_size;
|
||||
// Allocate enough memory for header/tag + original data because (a) we might
|
||||
// not be attempting compression at all, and (b) we might keep the original if
|
||||
// compression is insufficient. But we don't need the length prefix with
|
||||
// enable_split_merge. TODO: be smarter with CacheValueChunk to save an
|
||||
// allocation in the enable_split_merge case.
|
||||
size_t header_size = GetHeaderSize(data_size_original, enable_split_merge);
|
||||
CacheAllocationPtr allocation = AllocateBlock(
|
||||
header_size + data_size_original, cache_options_.memory_allocator.get());
|
||||
char* data_ptr = allocation.get() + header_size;
|
||||
Slice tagged_data(data_ptr - kTagSize, data_size_original + kTagSize);
|
||||
assert(tagged_data.data() >= allocation.get());
|
||||
|
||||
Status s = (*helper->saveto_cb)(value, 0, data_size, data_ptr);
|
||||
Status s = (*helper->saveto_cb)(value, 0, data_size_original, data_ptr);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
Slice val(data_ptr, data_size);
|
||||
|
||||
std::string compressed_val;
|
||||
if (cache_options_.compression_type != kNoCompression &&
|
||||
type == kNoCompression &&
|
||||
std::unique_ptr<char[]> tagged_compressed_data;
|
||||
CompressionType to_type = kNoCompression;
|
||||
if (compressor_ && from_type == kNoCompression &&
|
||||
!cache_options_.do_not_compress_roles.Contains(helper->role)) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_uncompressed_bytes, data_size);
|
||||
CompressionContext compression_context(cache_options_.compression_type,
|
||||
cache_options_.compression_opts);
|
||||
uint64_t sample_for_compression{0};
|
||||
CompressionInfo compression_info(
|
||||
cache_options_.compression_opts, compression_context,
|
||||
CompressionDict::GetEmptyDict(), cache_options_.compression_type,
|
||||
sample_for_compression);
|
||||
assert(source == CacheTier::kVolatileCompressedTier);
|
||||
|
||||
bool success =
|
||||
CompressData(val, compression_info,
|
||||
cache_options_.compress_format_version, &compressed_val);
|
||||
|
||||
if (!success) {
|
||||
return Status::Corruption("Error compressing value.");
|
||||
// TODO: consider malloc sizes for max acceptable compressed size
|
||||
// Or maybe max_compressed_bytes_per_kb
|
||||
size_t data_size_compressed = data_size_original - 1;
|
||||
tagged_compressed_data =
|
||||
std::make_unique<char[]>(data_size_compressed + kTagSize);
|
||||
s = compressor_->CompressBlock(Slice(data_ptr, data_size_original),
|
||||
tagged_compressed_data.get() + kTagSize,
|
||||
&data_size_compressed, &to_type,
|
||||
nullptr /*working_area*/);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
val = Slice(compressed_val);
|
||||
data_size = compressed_val.size();
|
||||
payload = EncodeVarint64(data_size_ptr, data_size);
|
||||
header_size = payload - header;
|
||||
total_size = header_size + data_size;
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes, data_size);
|
||||
|
||||
if (!cache_options_.enable_custom_split_merge) {
|
||||
ptr = AllocateBlock(total_size, cache_options_.memory_allocator.get());
|
||||
data_ptr = ptr.get() + header_size;
|
||||
memcpy(data_ptr, compressed_val.data(), data_size);
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_uncompressed_bytes,
|
||||
data_size_original);
|
||||
if (to_type == kNoCompression) {
|
||||
// Compression rejected or otherwise aborted/failed
|
||||
to_type = kNoCompression;
|
||||
tagged_compressed_data.reset();
|
||||
// TODO: consider separate counters for rejected compressions
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes,
|
||||
data_size_original);
|
||||
} else {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes,
|
||||
data_size_compressed);
|
||||
if (enable_split_merge) {
|
||||
// Only need tagged_data for copying into CacheValueChunks.
|
||||
tagged_data = Slice(tagged_compressed_data.get(),
|
||||
data_size_compressed + kTagSize);
|
||||
allocation.reset();
|
||||
} else {
|
||||
// Replace allocation with compressed version, copied from string
|
||||
header_size = GetHeaderSize(data_size_compressed, enable_split_merge);
|
||||
allocation = AllocateBlock(header_size + data_size_compressed,
|
||||
cache_options_.memory_allocator.get());
|
||||
data_ptr = allocation.get() + header_size;
|
||||
// Ignore unpopulated tag on tagged_compressed_data; will only be
|
||||
// populated on the new allocation.
|
||||
std::memcpy(data_ptr, tagged_compressed_data.get() + kTagSize,
|
||||
data_size_compressed);
|
||||
tagged_data =
|
||||
Slice(data_ptr - kTagSize, data_size_compressed + kTagSize);
|
||||
assert(tagged_data.data() >= allocation.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_real_count, 1);
|
||||
if (cache_options_.enable_custom_split_merge) {
|
||||
|
||||
// Save the tag fields
|
||||
const_cast<char*>(tagged_data.data())[0] = lossless_cast<char>(source);
|
||||
const_cast<char*>(tagged_data.data())[1] = lossless_cast<char>(
|
||||
source == CacheTier::kVolatileCompressedTier ? to_type : from_type);
|
||||
|
||||
if (enable_split_merge) {
|
||||
size_t split_charge{0};
|
||||
CacheValueChunk* value_chunks_head = SplitValueIntoChunks(
|
||||
val, cache_options_.compression_type, split_charge);
|
||||
return cache_->Insert(key, value_chunks_head, internal_helper,
|
||||
split_charge);
|
||||
CacheValueChunk* value_chunks_head =
|
||||
SplitValueIntoChunks(tagged_data, split_charge);
|
||||
s = cache_->Insert(key, value_chunks_head, internal_helper, split_charge);
|
||||
assert(s.ok()); // LRUCache::Insert() with handle==nullptr always OK
|
||||
} else {
|
||||
// Save the size prefix
|
||||
char* ptr = allocation.get();
|
||||
ptr = EncodeVarint64(ptr, tagged_data.size());
|
||||
assert(ptr == tagged_data.data());
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
size_t charge = malloc_usable_size(ptr.get());
|
||||
size_t charge = malloc_usable_size(allocation.get());
|
||||
#else
|
||||
size_t charge = total_size;
|
||||
size_t charge = tagged_data.size();
|
||||
#endif
|
||||
std::memcpy(ptr.get(), header, header_size);
|
||||
CacheAllocationPtr* buf = new CacheAllocationPtr(std::move(ptr));
|
||||
charge += sizeof(CacheAllocationPtr);
|
||||
return cache_->Insert(key, buf, internal_helper, charge);
|
||||
s = cache_->Insert(key, allocation.release(), internal_helper, charge);
|
||||
assert(s.ok()); // LRUCache::Insert() with handle==nullptr always OK
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
@@ -267,7 +302,17 @@ Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
Status CompressedSecondaryCache::InsertSaved(
|
||||
const Slice& key, const Slice& saved, CompressionType type = kNoCompression,
|
||||
CacheTier source = CacheTier::kVolatileTier) {
|
||||
if (source == CacheTier::kVolatileCompressedTier) {
|
||||
// Unexpected, would violate InsertInternal preconditions
|
||||
assert(source != CacheTier::kVolatileCompressedTier);
|
||||
return Status::OK();
|
||||
}
|
||||
if (type == kNoCompression) {
|
||||
// Not currently supported (why?)
|
||||
return Status::OK();
|
||||
}
|
||||
if (cache_options_.enable_custom_split_merge) {
|
||||
// We don't support custom split/merge for the tiered case (why?)
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -287,7 +332,7 @@ Status CompressedSecondaryCache::SetCapacity(size_t capacity) {
|
||||
MutexLock l(&capacity_mutex_);
|
||||
cache_options_.capacity = capacity;
|
||||
cache_->SetCapacity(capacity);
|
||||
disable_cache_ = capacity == 0;
|
||||
disable_cache_.StoreRelaxed(capacity == 0);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -317,9 +362,14 @@ std::string CompressedSecondaryCache::GetPrintableOptions() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// FIXME: this could use a lot of attention, including:
|
||||
// * Use allocator
|
||||
// * We shouldn't be worse than non-split; be more pro-actively aware of
|
||||
// internal fragmentation
|
||||
// * Consider a unified object/chunk structure that may or may not split
|
||||
// * Optimize size overhead of chunks
|
||||
CompressedSecondaryCache::CacheValueChunk*
|
||||
CompressedSecondaryCache::SplitValueIntoChunks(const Slice& value,
|
||||
CompressionType compression_type,
|
||||
size_t& charge) {
|
||||
assert(!value.empty());
|
||||
const char* src_ptr = value.data();
|
||||
@@ -340,15 +390,14 @@ CompressedSecondaryCache::SplitValueIntoChunks(const Slice& value,
|
||||
// size, or there is no compression.
|
||||
if (upper == malloc_bin_sizes_.begin() ||
|
||||
upper == malloc_bin_sizes_.end() ||
|
||||
*upper - predicted_chunk_size < malloc_bin_sizes_.front() ||
|
||||
compression_type == kNoCompression) {
|
||||
*upper - predicted_chunk_size < malloc_bin_sizes_.front()) {
|
||||
tmp_size = predicted_chunk_size;
|
||||
} else {
|
||||
tmp_size = *(--upper);
|
||||
}
|
||||
|
||||
CacheValueChunk* new_chunk =
|
||||
reinterpret_cast<CacheValueChunk*>(new char[tmp_size]);
|
||||
static_cast<CacheValueChunk*>(static_cast<void*>(new char[tmp_size]));
|
||||
current_chunk->next = new_chunk;
|
||||
current_chunk = current_chunk->next;
|
||||
actual_chunk_size = tmp_size - sizeof(CacheValueChunk) + 1;
|
||||
@@ -363,28 +412,24 @@ CompressedSecondaryCache::SplitValueIntoChunks(const Slice& value,
|
||||
return dummy_head.next;
|
||||
}
|
||||
|
||||
CacheAllocationPtr CompressedSecondaryCache::MergeChunksIntoValue(
|
||||
const void* chunks_head, size_t& charge) {
|
||||
const CacheValueChunk* head =
|
||||
reinterpret_cast<const CacheValueChunk*>(chunks_head);
|
||||
std::string CompressedSecondaryCache::MergeChunksIntoValue(
|
||||
const CacheValueChunk* head) {
|
||||
const CacheValueChunk* current_chunk = head;
|
||||
charge = 0;
|
||||
size_t total_size = 0;
|
||||
while (current_chunk != nullptr) {
|
||||
charge += current_chunk->size;
|
||||
total_size += current_chunk->size;
|
||||
current_chunk = current_chunk->next;
|
||||
}
|
||||
|
||||
CacheAllocationPtr ptr =
|
||||
AllocateBlock(charge, cache_options_.memory_allocator.get());
|
||||
std::string result;
|
||||
result.reserve(total_size);
|
||||
current_chunk = head;
|
||||
size_t pos{0};
|
||||
while (current_chunk != nullptr) {
|
||||
memcpy(ptr.get() + pos, current_chunk->data, current_chunk->size);
|
||||
pos += current_chunk->size;
|
||||
result.append(current_chunk->data, current_chunk->size);
|
||||
current_chunk = current_chunk->next;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
assert(result.size() == total_size);
|
||||
return result;
|
||||
}
|
||||
|
||||
const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
|
||||
@@ -398,16 +443,16 @@ const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
|
||||
CacheValueChunk* tmp_chunk = chunks_head;
|
||||
chunks_head = chunks_head->next;
|
||||
tmp_chunk->Free();
|
||||
obj = nullptr;
|
||||
}
|
||||
}};
|
||||
return &kHelper;
|
||||
} else {
|
||||
static const Cache::CacheItemHelper kHelper{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr obj, MemoryAllocator* /*alloc*/) {
|
||||
delete static_cast<CacheAllocationPtr*>(obj);
|
||||
obj = nullptr;
|
||||
[](Cache::ObjectPtr obj, MemoryAllocator* alloc) {
|
||||
if (obj != nullptr) {
|
||||
CacheAllocationDeleter{alloc}(static_cast<char*>(obj));
|
||||
}
|
||||
}};
|
||||
return &kHelper;
|
||||
}
|
||||
@@ -418,12 +463,7 @@ size_t CompressedSecondaryCache::TEST_GetCharge(const Slice& key) {
|
||||
if (lru_handle == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t charge = cache_->GetCharge(lru_handle);
|
||||
if (cache_->Value(lru_handle) != nullptr &&
|
||||
!cache_options_.enable_custom_split_merge) {
|
||||
charge -= 10;
|
||||
}
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
return charge;
|
||||
}
|
||||
|
||||
Vendored
+7
-11
@@ -10,13 +10,12 @@
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "cache/lru_cache.h"
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "rocksdb/advanced_compression.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/atomic.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -124,14 +123,9 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
// Split value into chunks to better fit into jemalloc bins. The chunks
|
||||
// are stored in CacheValueChunk and extra charge is needed for each chunk,
|
||||
// so the cache charge is recalculated here.
|
||||
CacheValueChunk* SplitValueIntoChunks(const Slice& value,
|
||||
CompressionType compression_type,
|
||||
size_t& charge);
|
||||
CacheValueChunk* SplitValueIntoChunks(const Slice& value, size_t& charge);
|
||||
|
||||
// After merging chunks, the extra charge for each chunk is removed, so
|
||||
// the charge is recalculated.
|
||||
CacheAllocationPtr MergeChunksIntoValue(const void* chunks_head,
|
||||
size_t& charge);
|
||||
std::string MergeChunksIntoValue(const CacheValueChunk* head);
|
||||
|
||||
bool MaybeInsertDummy(const Slice& key);
|
||||
|
||||
@@ -145,9 +139,11 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
const Cache::CacheItemHelper* GetHelper(bool enable_custom_split_merge) const;
|
||||
std::shared_ptr<Cache> cache_;
|
||||
CompressedSecondaryCacheOptions cache_options_;
|
||||
std::unique_ptr<Compressor> compressor_;
|
||||
std::shared_ptr<Decompressor> decompressor_;
|
||||
mutable port::Mutex capacity_mutex_;
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
|
||||
bool disable_cache_;
|
||||
RelaxedAtomic<bool> disable_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+116
-47
@@ -24,6 +24,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
using secondary_cache_test_util::GetTestingCacheTypes;
|
||||
using secondary_cache_test_util::WithCacheType;
|
||||
|
||||
// Read and reset a statistic
|
||||
template <typename T>
|
||||
T Pop(T& var) {
|
||||
T ret = var;
|
||||
var = T();
|
||||
return ret;
|
||||
}
|
||||
|
||||
// 16 bytes for HCC compatibility
|
||||
const std::string key0 = "____ ____key0";
|
||||
const std::string key1 = "____ ____key1";
|
||||
@@ -51,7 +59,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
|
||||
Random rnd(301);
|
||||
// Insert and Lookup the item k1 for the first time.
|
||||
std::string str1(rnd.RandomString(1000));
|
||||
std::string str1 = test::CompressibleString(&rnd, 0.5, 1000);
|
||||
TestItem item1(str1.data(), str1.length());
|
||||
// A dummy handle is inserted if the item is inserted for the first time.
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
@@ -68,7 +76,14 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
|
||||
ASSERT_GT(comp_sec_cache->TEST_GetCharge(key1), 1000);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_GT(comp_sec_cache->TEST_GetCharge(key1), str1.length() / 4);
|
||||
ASSERT_LT(comp_sec_cache->TEST_GetCharge(key1), str1.length() * 3 / 4);
|
||||
} else {
|
||||
ASSERT_GE(comp_sec_cache->TEST_GetCharge(key1), str1.length());
|
||||
// NOTE: split-merge is worse (1048 vs. 1024)
|
||||
ASSERT_LE(comp_sec_cache->TEST_GetCharge(key1), 1048U);
|
||||
}
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
||||
@@ -76,10 +91,13 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_NE(handle1_2, nullptr);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
1000);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
1007);
|
||||
ASSERT_EQ(
|
||||
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
|
||||
str1.length());
|
||||
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
str1.length() * 3 / 4);
|
||||
ASSERT_GT(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
|
||||
str1.length() / 4);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
@@ -97,7 +115,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_EQ(handle1_3, nullptr);
|
||||
|
||||
// Insert and Lookup the item k2.
|
||||
std::string str2(rnd.RandomString(1000));
|
||||
std::string str2 = test::CompressibleString(&rnd, 0.5, 1017);
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
||||
@@ -109,10 +127,13 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
2000);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
2014);
|
||||
ASSERT_EQ(
|
||||
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
|
||||
str2.length());
|
||||
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
str2.length() * 3 / 4);
|
||||
ASSERT_GT(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
|
||||
str2.length() / 4);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
@@ -126,9 +147,48 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_NE(val2, nullptr);
|
||||
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
|
||||
|
||||
// Release handles
|
||||
std::vector<SecondaryCacheResultHandle*> handles = {handle1_2.get(),
|
||||
handle2_2.get()};
|
||||
sec_cache->WaitAll(handles);
|
||||
handle1_2.reset();
|
||||
handle2_2.reset();
|
||||
|
||||
// Insert and Lookup a non-compressible item k3.
|
||||
std::string str3 = rnd.RandomBinaryString(480);
|
||||
TestItem item3(str3.data(), str3.length());
|
||||
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelper(), false));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle3_1 =
|
||||
sec_cache->Lookup(key3, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle3_1, nullptr);
|
||||
|
||||
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelper(), false));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 3);
|
||||
if (sec_cache_is_compressed) {
|
||||
// TODO: consider a compression rejected stat?
|
||||
ASSERT_EQ(
|
||||
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
|
||||
str3.length());
|
||||
ASSERT_EQ(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
|
||||
str3.length());
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle3_2 =
|
||||
sec_cache->Lookup(key3, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_NE(handle3_2, nullptr);
|
||||
std::unique_ptr<TestItem> val3 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle3_2->Value()));
|
||||
ASSERT_NE(val3, nullptr);
|
||||
ASSERT_EQ(memcmp(val3->Buf(), item3.Buf(), item3.Size()), 0);
|
||||
|
||||
EXPECT_GE(comp_sec_cache->TEST_GetCharge(key3), str3.length());
|
||||
EXPECT_LE(comp_sec_cache->TEST_GetCharge(key3), 512);
|
||||
|
||||
sec_cache.reset();
|
||||
}
|
||||
@@ -178,8 +238,9 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
secondary_cache_opts.capacity = 1100;
|
||||
secondary_cache_opts.capacity = 1400;
|
||||
secondary_cache_opts.num_shard_bits = 0;
|
||||
secondary_cache_opts.strict_capacity_limit = true;
|
||||
std::shared_ptr<SecondaryCache> sec_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
@@ -193,7 +254,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper(), false));
|
||||
|
||||
// Insert and Lookup the second item.
|
||||
std::string str2(rnd.RandomString(200));
|
||||
std::string str2(rnd.RandomString(500));
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
// Insert a dummy handle, k1 is not evicted.
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
@@ -201,16 +262,23 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
std::unique_ptr<TestItem> val1{static_cast<TestItem*>(handle1->Value())};
|
||||
ASSERT_NE(val1, nullptr);
|
||||
ASSERT_EQ(val1->ToString(), str1);
|
||||
handle1.reset();
|
||||
|
||||
// Insert k2 and k1 is evicted.
|
||||
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper(), false));
|
||||
handle1 =
|
||||
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2 =
|
||||
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
||||
/*stats=*/nullptr, kept_in_sec_cache);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
std::unique_ptr<TestItem> val2 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2->Value()));
|
||||
std::unique_ptr<TestItem> val2{static_cast<TestItem*>(handle2->Value())};
|
||||
ASSERT_NE(val2, nullptr);
|
||||
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
|
||||
|
||||
@@ -232,7 +300,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
// Save Fails.
|
||||
std::string str3 = rnd.RandomString(10);
|
||||
TestItem item3(str3.data(), str3.length());
|
||||
// The Status is OK because a dummy handle is inserted.
|
||||
// The first Status is OK because a dummy handle is inserted.
|
||||
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelperFail(), false));
|
||||
ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail(), false));
|
||||
|
||||
@@ -265,11 +333,11 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
|
||||
get_perf_context()->Reset();
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
std::string str1 = test::CompressibleString(&rnd, 0.5, 1001);
|
||||
auto item1_1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert(key1, item1_1, GetHelper(), str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1012);
|
||||
std::string str2 = test::CompressibleString(&rnd, 0.5, 1012);
|
||||
auto item2_1 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's dummy item.
|
||||
@@ -278,7 +346,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
|
||||
std::string str3 = rnd.RandomString(1024);
|
||||
std::string str3 = test::CompressibleString(&rnd, 0.5, 1024);
|
||||
auto item3_1 = new TestItem(str3.data(), str3.length());
|
||||
// After this Insert, primary cache contains k3 and secondary cache contains
|
||||
// k1's dummy item and k2's dummy item.
|
||||
@@ -297,10 +365,13 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_OK(cache->Insert(key2, item2_2, GetHelper(), str2.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
ASSERT_EQ(
|
||||
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
|
||||
str1.length());
|
||||
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
str1.length());
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
1008);
|
||||
ASSERT_GT(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
|
||||
str1.length() / 10);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
@@ -312,10 +383,13 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
ASSERT_OK(cache->Insert(key3, item3_2, GetHelper(), str3.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
str1.length() + str2.length());
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
2027);
|
||||
ASSERT_EQ(
|
||||
Pop(get_perf_context()->compressed_sec_cache_uncompressed_bytes),
|
||||
str2.length());
|
||||
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
str2.length());
|
||||
ASSERT_GT(Pop(get_perf_context()->compressed_sec_cache_compressed_bytes),
|
||||
str2.length() / 10);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
@@ -641,8 +715,7 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
size_t str_size{8500};
|
||||
std::string str = rnd.RandomString(static_cast<int>(str_size));
|
||||
size_t charge{0};
|
||||
CacheValueChunk* chunks_head =
|
||||
sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge);
|
||||
CacheValueChunk* chunks_head = sec_cache->SplitValueIntoChunks(str, charge);
|
||||
ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1));
|
||||
|
||||
CacheValueChunk* current_chunk = chunks_head;
|
||||
@@ -688,12 +761,9 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(
|
||||
CompressedSecondaryCacheOptions(1000, 0, true, 0.5, 0.0));
|
||||
size_t charge{0};
|
||||
CacheAllocationPtr value =
|
||||
sec_cache->MergeChunksIntoValue(chunks_head, charge);
|
||||
ASSERT_EQ(charge, size1 + size2 + size3);
|
||||
std::string value_str{value.get(), charge};
|
||||
ASSERT_EQ(strcmp(value_str.data(), str.data()), 0);
|
||||
std::string value_str = sec_cache->MergeChunksIntoValue(chunks_head);
|
||||
ASSERT_EQ(value_str.size(), size1 + size2 + size3);
|
||||
ASSERT_EQ(value_str, str);
|
||||
|
||||
while (chunks_head != nullptr) {
|
||||
CacheValueChunk* tmp_chunk = chunks_head;
|
||||
@@ -725,15 +795,12 @@ class CompressedSecondaryCacheTestBase : public testing::Test,
|
||||
size_t str_size{8500};
|
||||
std::string str = rnd.RandomString(static_cast<int>(str_size));
|
||||
size_t charge{0};
|
||||
CacheValueChunk* chunks_head =
|
||||
sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge);
|
||||
CacheValueChunk* chunks_head = sec_cache->SplitValueIntoChunks(str, charge);
|
||||
ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1));
|
||||
|
||||
CacheAllocationPtr value =
|
||||
sec_cache->MergeChunksIntoValue(chunks_head, charge);
|
||||
ASSERT_EQ(charge, str_size);
|
||||
std::string value_str{value.get(), charge};
|
||||
ASSERT_EQ(strcmp(value_str.data(), str.data()), 0);
|
||||
std::string value_str = sec_cache->MergeChunksIntoValue(chunks_head);
|
||||
ASSERT_EQ(value_str.size(), str_size);
|
||||
ASSERT_EQ(value_str, str);
|
||||
|
||||
sec_cache->GetHelper(true)->del_cb(chunks_head, /*alloc*/ nullptr);
|
||||
}
|
||||
@@ -896,8 +963,8 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
|
||||
|
||||
std::shared_ptr<SecondaryCache> sec_cache = NewCompressedSecondaryCache(opts);
|
||||
|
||||
// Fixed seed to ensure consistent compressibility (doesn't compress)
|
||||
std::string junk(Random(301).RandomString(1000));
|
||||
Random rnd(301);
|
||||
std::string junk = test::CompressibleString(&rnd, 0.5, 1000);
|
||||
|
||||
for (uint32_t i = 0; i < kNumCacheEntryRoles; ++i) {
|
||||
CacheEntryRole role = static_cast<CacheEntryRole>(i);
|
||||
@@ -930,9 +997,11 @@ TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
|
||||
sec_cache_is_compressed_ && !do_not_compress.Contains(role);
|
||||
if (compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
1000);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
1007);
|
||||
junk.length());
|
||||
ASSERT_LT(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
junk.length() * 3 / 4);
|
||||
ASSERT_GT(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
junk.length() / 4);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
|
||||
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
+16
-10
@@ -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.
|
||||
//
|
||||
@@ -121,7 +121,14 @@ CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
|
||||
assert(s.ok());
|
||||
assert(placeholder_usage_ == 0);
|
||||
assert(reserved_usage_ == 0);
|
||||
assert(pri_cache_res_->GetTotalMemoryUsed() == sec_capacity);
|
||||
if (pri_cache_res_->GetTotalMemoryUsed() != sec_capacity) {
|
||||
fprintf(stdout,
|
||||
"~CacheWithSecondaryAdapter: Primary cache reservation: "
|
||||
"%zu, Secondary cache capacity: %zu, "
|
||||
"Secondary cache reserved: %zu\n",
|
||||
pri_cache_res_->GetTotalMemoryUsed(), sec_capacity,
|
||||
sec_reserved_);
|
||||
}
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
@@ -479,12 +486,10 @@ const char* CacheWithSecondaryAdapter::Name() const {
|
||||
// as well. At the moment, we don't have a good way of handling the case
|
||||
// where the new capacity < total cache reservations.
|
||||
void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
|
||||
size_t sec_capacity = static_cast<size_t>(
|
||||
capacity * (distribute_cache_res_ ? sec_cache_res_ratio_ : 0.0));
|
||||
size_t old_sec_capacity = 0;
|
||||
|
||||
if (distribute_cache_res_) {
|
||||
MutexLock m(&cache_res_mutex_);
|
||||
size_t sec_capacity = static_cast<size_t>(capacity * sec_cache_res_ratio_);
|
||||
size_t old_sec_capacity = 0;
|
||||
|
||||
Status s = secondary_cache_->GetCapacity(old_sec_capacity);
|
||||
if (!s.ok()) {
|
||||
@@ -579,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;
|
||||
@@ -603,6 +608,7 @@ Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
|
||||
// cache utilization (increase in capacity - increase in share of cache
|
||||
// reservation)
|
||||
// 3. Increase secondary cache capacity
|
||||
assert(new_sec_reserved >= sec_reserved_);
|
||||
s = secondary_cache_->Deflate(new_sec_reserved - sec_reserved_);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(
|
||||
@@ -615,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 %*
|
||||
+61
-15
@@ -11,18 +11,29 @@ CRASHTEST_MAKE=$(MAKE) -f crash_test.mk
|
||||
CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --cleanup_cmd='$(DB_CLEANUP_CMD)'
|
||||
|
||||
.PHONY: crash_test crash_test_with_atomic_flush crash_test_with_txn \
|
||||
crash_test_with_wc_txn crash_test_with_wp_txn crash_test_with_wup_txn \
|
||||
crash_test_with_best_efforts_recovery crash_test_with_ts \
|
||||
crash_test_with_multiops_wc_txn \
|
||||
crash_test_with_multiops_wp_txn \
|
||||
crash_test_with_multiops_wup_txn \
|
||||
crash_test_with_optimistic_txn \
|
||||
crash_test_with_tiered_storage \
|
||||
blackbox_crash_test blackbox_crash_test_with_atomic_flush \
|
||||
blackbox_crash_test_with_wc_txn blackbox_crash_test_with_wp_txn \
|
||||
blackbox_crash_test_with_wup_txn \
|
||||
blackbox_crash_test_with_txn blackbox_crash_test_with_ts \
|
||||
blackbox_crash_test_with_best_efforts_recovery \
|
||||
whitebox_crash_test whitebox_crash_test_with_atomic_flush \
|
||||
whitebox_crash_test_with_txn whitebox_crash_test_with_ts \
|
||||
blackbox_crash_test_with_multiops_wc_txn \
|
||||
blackbox_crash_test_with_multiops_wp_txn \
|
||||
crash_test_with_tiered_storage blackbox_crash_test_with_tiered_storage \
|
||||
whitebox_crash_test_with_tiered_storage \
|
||||
whitebox_crash_test_with_optimistic_txn \
|
||||
blackbox_crash_test_with_multiops_wup_txn \
|
||||
blackbox_crash_test_with_optimistic_txn \
|
||||
blackbox_crash_test_with_tiered_storage \
|
||||
whitebox_crash_test whitebox_crash_test_with_atomic_flush \
|
||||
whitebox_crash_test_with_wc_txn whitebox_crash_test_with_wp_txn \
|
||||
whitebox_crash_test_with_wup_txn \
|
||||
whitebox_crash_test_with_txn whitebox_crash_test_with_ts \
|
||||
whitebox_crash_test_with_optimistic_txn \
|
||||
whitebox_crash_test_with_tiered_storage \
|
||||
|
||||
crash_test: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
@@ -34,10 +45,20 @@ crash_test_with_atomic_flush: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_atomic_flush
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_atomic_flush
|
||||
|
||||
crash_test_with_txn: $(DB_STRESS_CMD)
|
||||
crash_test_with_wc_txn: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_txn
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_wc_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_wc_txn
|
||||
|
||||
crash_test_with_wp_txn: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_wp_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_wp_txn
|
||||
|
||||
crash_test_with_wup_txn: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_wup_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_wup_txn
|
||||
|
||||
crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
@@ -62,6 +83,9 @@ crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
|
||||
crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wp_txn
|
||||
|
||||
crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wup_txn
|
||||
|
||||
blackbox_crash_test: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(CRASHTEST_PY) blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
@@ -69,8 +93,14 @@ blackbox_crash_test: $(DB_STRESS_CMD)
|
||||
blackbox_crash_test_with_atomic_flush: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --cf_consistency blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
blackbox_crash_test_with_wc_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn blackbox --txn_write_policy 0 $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_wp_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn blackbox --txn_write_policy 1 $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_wup_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn blackbox --txn_write_policy 2 $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_best_efforts_recovery: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_best_efforts_recovery blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
@@ -79,10 +109,13 @@ blackbox_crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --enable_ts blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_multiops_txn --write_policy write_committed blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(CRASHTEST_PY) --test_multiops_txn --txn_write_policy 0 blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_multiops_txn --write_policy write_prepared blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(CRASHTEST_PY) --test_multiops_txn --txn_write_policy 1 blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_multiops_wup_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_multiops_txn --txn_write_policy 2 blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_tiered_storage blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
@@ -104,9 +137,17 @@ whitebox_crash_test_with_atomic_flush: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --cf_consistency whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
whitebox_crash_test_with_wc_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn whitebox --txn_write_policy 0 \
|
||||
--random_kill_odd $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_wp_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn whitebox --txn_write_policy 1 \
|
||||
--random_kill_odd $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_wup_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn whitebox --txn_write_policy 2 \
|
||||
--random_kill_odd $(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --enable_ts whitebox --random_kill_odd \
|
||||
@@ -119,3 +160,8 @@ whitebox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
whitebox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --optimistic_txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
# Old names DEPRECATED
|
||||
crash_test_with_txn: crash_test_with_wc_txn
|
||||
whitebox_crash_test_with_txn: whitebox_crash_test_with_wc_txn
|
||||
blackbox_crash_test_with_txn: blackbox_crash_test_with_wc_txn
|
||||
+27
-23
@@ -42,9 +42,9 @@ Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
|
||||
void ArenaWrappedDBIter::Init(
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iteration,
|
||||
uint64_t version_number, ReadCallback* read_callback,
|
||||
ColumnFamilyHandleImpl* cfh, bool expose_blob_index, bool allow_refresh) {
|
||||
const SequenceNumber& sequence, uint64_t version_number,
|
||||
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
|
||||
bool expose_blob_index, bool allow_refresh, ReadOnlyMemTable* active_mem) {
|
||||
read_options_ = read_options;
|
||||
if (!CheckFSFeatureSupport(env->GetFileSystem().get(),
|
||||
FSSupportedOps::kAsyncIO)) {
|
||||
@@ -52,15 +52,14 @@ void ArenaWrappedDBIter::Init(
|
||||
}
|
||||
read_options_.total_order_seek |= ioptions.prefix_seek_opt_in_only;
|
||||
|
||||
auto mem = arena_.AllocateAligned(sizeof(DBIter));
|
||||
db_iter_ = new (mem) DBIter(env, read_options_, ioptions, mutable_cf_options,
|
||||
ioptions.user_comparator,
|
||||
/* iter */ nullptr, version, sequence, true,
|
||||
max_sequential_skip_in_iteration, read_callback,
|
||||
cfh, expose_blob_index);
|
||||
db_iter_ = DBIter::NewIter(
|
||||
env, read_options_, ioptions, mutable_cf_options,
|
||||
ioptions.user_comparator, /*internal_iter=*/nullptr, version, sequence,
|
||||
read_callback, active_mem, cfh, expose_blob_index, &arena_);
|
||||
|
||||
sv_number_ = version_number;
|
||||
allow_refresh_ = allow_refresh;
|
||||
allow_mark_memtable_for_flush_ = active_mem;
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
}
|
||||
|
||||
@@ -166,9 +165,8 @@ void ArenaWrappedDBIter::DoRefresh(const Snapshot* snapshot,
|
||||
read_callback_->Refresh(read_seq);
|
||||
}
|
||||
Init(env, read_options_, cfd->ioptions(), sv->mutable_cf_options, sv->current,
|
||||
read_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number, read_callback_, cfh_, expose_blob_index_,
|
||||
allow_refresh_);
|
||||
read_seq, sv->version_number, read_callback_, cfh_, expose_blob_index_,
|
||||
allow_refresh_, allow_mark_memtable_for_flush_ ? sv->mem : nullptr);
|
||||
|
||||
InternalIterator* internal_iter = db_impl->NewInternalIterator(
|
||||
read_options_, cfd, sv, &arena_, read_seq,
|
||||
@@ -253,20 +251,26 @@ Status ArenaWrappedDBIter::Refresh(const Snapshot* snapshot) {
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
|
||||
uint64_t version_number, ReadCallback* read_callback,
|
||||
ColumnFamilyHandleImpl* cfh, bool expose_blob_index, bool allow_refresh) {
|
||||
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
|
||||
iter->Init(env, read_options, ioptions, mutable_cf_options, version, sequence,
|
||||
max_sequential_skip_in_iterations, version_number, read_callback,
|
||||
cfh, expose_blob_index, allow_refresh);
|
||||
Env* env, const ReadOptions& read_options, ColumnFamilyHandleImpl* cfh,
|
||||
SuperVersion* sv, const SequenceNumber& sequence,
|
||||
ReadCallback* read_callback, DBImpl* db_impl, bool expose_blob_index,
|
||||
bool allow_refresh, bool allow_mark_memtable_for_flush) {
|
||||
ArenaWrappedDBIter* db_iter = new ArenaWrappedDBIter();
|
||||
db_iter->Init(env, read_options, cfh->cfd()->ioptions(),
|
||||
sv->mutable_cf_options, sv->current, sequence,
|
||||
sv->version_number, read_callback, cfh, expose_blob_index,
|
||||
allow_refresh,
|
||||
allow_mark_memtable_for_flush ? sv->mem : nullptr);
|
||||
if (cfh != nullptr && allow_refresh) {
|
||||
iter->StoreRefreshInfo(cfh, read_callback, expose_blob_index);
|
||||
db_iter->StoreRefreshInfo(cfh, read_callback, expose_blob_index);
|
||||
}
|
||||
|
||||
return iter;
|
||||
InternalIterator* internal_iter = db_impl->NewInternalIterator(
|
||||
db_iter->GetReadOptions(), cfh->cfd(), sv, db_iter->GetArena(), sequence,
|
||||
/*allow_unprepared_value=*/true, db_iter);
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+14
-13
@@ -19,7 +19,6 @@
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -99,13 +98,19 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
|
||||
bool PrepareValue() override { return db_iter_->PrepareValue(); }
|
||||
|
||||
void Prepare(const MultiScanArgs& scan_opts) override {
|
||||
db_iter_->Prepare(scan_opts);
|
||||
}
|
||||
|
||||
// FIXME: we could just pass SV in for mutable cf option, version and version
|
||||
// number, but this is used by SstFileReader which does not have a SV.
|
||||
void Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
const SequenceNumber& sequence, uint64_t version_number,
|
||||
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
|
||||
bool expose_blob_index, bool allow_refresh);
|
||||
bool expose_blob_index, bool allow_refresh,
|
||||
ReadOnlyMemTable* active_mem);
|
||||
|
||||
// Store some parameters so we can refresh the iterator at a later point
|
||||
// with these same params
|
||||
@@ -128,20 +133,16 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
ReadCallback* read_callback_;
|
||||
bool expose_blob_index_ = false;
|
||||
bool allow_refresh_ = true;
|
||||
bool allow_mark_memtable_for_flush_ = true;
|
||||
// If this is nullptr, it means the mutable memtable does not contain range
|
||||
// tombstone when added under this DBIter.
|
||||
std::unique_ptr<TruncatedRangeDelIterator>* memtable_range_tombstone_iter_ =
|
||||
nullptr;
|
||||
};
|
||||
|
||||
// Generate the arena wrapped iterator class.
|
||||
// `cfh` is used for reneweal. If left null, renewal will not
|
||||
// be supported.
|
||||
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
|
||||
uint64_t version_number, ReadCallback* read_callback,
|
||||
ColumnFamilyHandleImpl* cfh = nullptr, bool expose_blob_index = false,
|
||||
bool allow_refresh = true);
|
||||
Env* env, const ReadOptions& read_options, ColumnFamilyHandleImpl* cfh,
|
||||
SuperVersion* sv, const SequenceNumber& sequence,
|
||||
ReadCallback* read_callback, DBImpl* db_impl, bool expose_blob_index,
|
||||
bool allow_refresh, bool allow_mark_memtable_for_flush);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -267,10 +267,9 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
// TODO: allow user CompressionOptions, including max_compressed_bytes_per_kb
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(blob_compression_type_, opts);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
blob_compression_type_, sample_for_compression);
|
||||
blob_compression_type_);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
|
||||
@@ -279,8 +278,8 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
{
|
||||
StopWatch stop_watch(immutable_options_->clock, immutable_options_->stats,
|
||||
BLOB_DB_COMPRESSION_MICROS);
|
||||
success =
|
||||
CompressData(*blob, info, compression_format_version, compressed_blob);
|
||||
success = OLD_CompressData(*blob, info, compression_format_version,
|
||||
compressed_blob);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
|
||||
@@ -405,10 +405,9 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(kSnappyCompression, opts);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
kSnappyCompression, sample_for_compression);
|
||||
kSnappyCompression);
|
||||
|
||||
std::string compressed_value;
|
||||
ASSERT_TRUE(Snappy_Compress(info, uncompressed_value.data(),
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
@@ -250,7 +250,8 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
Status s;
|
||||
|
||||
IOOptions io_options;
|
||||
s = file_reader->PrepareIOOptions(read_options, io_options);
|
||||
IODebugContext dbg;
|
||||
s = file_reader->PrepareIOOptions(read_options, io_options, &dbg);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -259,13 +260,13 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
constexpr char* scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
|
||||
aligned_buf);
|
||||
aligned_buf, &dbg);
|
||||
} else {
|
||||
buf->reset(new char[read_size]);
|
||||
constexpr AlignedBuf* aligned_scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
|
||||
aligned_scratch);
|
||||
aligned_scratch, &dbg);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -334,7 +335,8 @@ Status BlobFileReader::GetBlob(
|
||||
constexpr bool for_compaction = true;
|
||||
|
||||
IOOptions io_options;
|
||||
s = file_reader_->PrepareIOOptions(read_options, io_options);
|
||||
IODebugContext dbg;
|
||||
s = file_reader_->PrepareIOOptions(read_options, io_options, &dbg);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -463,10 +465,11 @@ void BlobFileReader::MultiGetBlob(
|
||||
PERF_COUNTER_ADD(blob_read_count, num_blobs);
|
||||
PERF_COUNTER_ADD(blob_read_byte, total_len);
|
||||
IOOptions opts;
|
||||
s = file_reader_->PrepareIOOptions(read_options, opts);
|
||||
IODebugContext dbg;
|
||||
s = file_reader_->PrepareIOOptions(read_options, opts, &dbg);
|
||||
if (s.ok()) {
|
||||
s = file_reader_->MultiRead(opts, read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr);
|
||||
direct_io ? &aligned_buf : nullptr, &dbg);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
for (auto& req : read_reqs) {
|
||||
@@ -602,9 +605,9 @@ Status BlobFileReader::UncompressBlobIfNeeded(
|
||||
{
|
||||
PERF_TIMER_GUARD(blob_decompress_time);
|
||||
StopWatch stop_watch(clock, statistics, BLOB_DB_DECOMPRESSION_MICROS);
|
||||
output = UncompressData(info, value_slice.data(), value_slice.size(),
|
||||
&uncompressed_size, compression_format_version,
|
||||
allocator);
|
||||
output = OLD_UncompressData(info, value_slice.data(), value_slice.size(),
|
||||
&uncompressed_size, compression_format_version,
|
||||
allocator);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
|
||||
@@ -75,15 +75,14 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
compression);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
ASSERT_TRUE(CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
ASSERT_TRUE(OLD_CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
blobs_to_write[i] = compressed_blobs[i];
|
||||
blob_sizes[i] = compressed_blobs[i].size();
|
||||
}
|
||||
|
||||
@@ -77,15 +77,14 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
compression);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
ASSERT_TRUE(CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
ASSERT_TRUE(OLD_CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
blobs_to_write[i] = compressed_blobs[i];
|
||||
blob_sizes[i] = compressed_blobs[i].size();
|
||||
}
|
||||
|
||||
+20
-8
@@ -74,8 +74,8 @@ Status BuildTable(
|
||||
EventLogger* event_logger, int job_id, TableProperties* table_properties,
|
||||
Env::WriteLifeTimeHint write_hint, const std::string* full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback, Version* version,
|
||||
uint64_t* num_input_entries, uint64_t* memtable_payload_bytes,
|
||||
uint64_t* memtable_garbage_bytes) {
|
||||
uint64_t* memtable_payload_bytes, uint64_t* memtable_garbage_bytes,
|
||||
InternalStats::CompactionStats* flush_stats) {
|
||||
assert((tboptions.column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
tboptions.column_family_name.empty());
|
||||
@@ -201,8 +201,7 @@ Status BuildTable(
|
||||
CompactionIterator c_iter(
|
||||
iter, ucmp, &merge, kMaxSequenceNumber, &snapshots, earliest_snapshot,
|
||||
earliest_write_conflict_snapshot, job_snapshot, snapshot_checker, env,
|
||||
ShouldReportDetailedTime(env, ioptions.stats),
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
ShouldReportDetailedTime(env, ioptions.stats), range_del_agg.get(),
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors,
|
||||
ioptions.enforce_single_del_contracts,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
@@ -218,8 +217,7 @@ Status BuildTable(
|
||||
const Slice& key = c_iter.key();
|
||||
const Slice& value = c_iter.value();
|
||||
ParsedInternalKey ikey = c_iter.ikey();
|
||||
key_after_flush_buf.assign(key.data(), key.size());
|
||||
Slice key_after_flush = key_after_flush_buf;
|
||||
Slice key_after_flush = key;
|
||||
Slice value_after_flush = value;
|
||||
|
||||
if (ikey.type == kTypeValuePreferredSeqno) {
|
||||
@@ -237,6 +235,7 @@ Status BuildTable(
|
||||
std::min(smallest_preferred_seqno, preferred_seqno);
|
||||
} else {
|
||||
// Cannot get a useful preferred seqno, convert it to a kTypeValue.
|
||||
key_after_flush_buf.assign(key.data(), key.size());
|
||||
UpdateInternalKey(&key_after_flush_buf, ikey.sequence, kTypeValue);
|
||||
ikey = ParsedInternalKey(ikey.user_key, ikey.sequence, kTypeValue);
|
||||
key_after_flush = key_after_flush_buf;
|
||||
@@ -253,6 +252,10 @@ Status BuildTable(
|
||||
}
|
||||
builder->Add(key_after_flush, value_after_flush);
|
||||
|
||||
if (flush_stats) {
|
||||
flush_stats->num_output_records++;
|
||||
}
|
||||
|
||||
s = meta->UpdateBoundaries(key_after_flush, value_after_flush,
|
||||
ikey.sequence, ikey.type);
|
||||
if (!s.ok()) {
|
||||
@@ -284,6 +287,9 @@ Status BuildTable(
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
std::pair<InternalKey, Slice> kv = tombstone.Serialize();
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
if (flush_stats) {
|
||||
flush_stats->num_output_records++;
|
||||
}
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone_end, tombstone.seq_,
|
||||
tboptions.internal_comparator);
|
||||
@@ -305,9 +311,9 @@ Status BuildTable(
|
||||
|
||||
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
|
||||
const bool empty = builder->IsEmpty();
|
||||
if (num_input_entries != nullptr) {
|
||||
if (flush_stats) {
|
||||
assert(c_iter.HasNumInputEntryScanned());
|
||||
*num_input_entries =
|
||||
flush_stats->num_input_records =
|
||||
c_iter.NumInputEntryScanned() + num_unfragmented_tombstones;
|
||||
}
|
||||
if (!s.ok() || empty) {
|
||||
@@ -334,6 +340,12 @@ 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;
|
||||
meta->tail_size = builder->GetTailSize();
|
||||
|
||||
+5
-4
@@ -10,6 +10,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/seqno_to_time_mapping.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
@@ -34,7 +35,6 @@ class SnapshotChecker;
|
||||
class TableCache;
|
||||
class TableBuilder;
|
||||
class WritableFileWriter;
|
||||
class InternalStats;
|
||||
class BlobFileCompletionCallback;
|
||||
|
||||
// Convenience function for NewTableBuilder on the embedded table_factory.
|
||||
@@ -49,6 +49,7 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
//
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown.
|
||||
// @param flush_stats treat flush as level 0 compaction in internal stats
|
||||
Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
|
||||
@@ -69,8 +70,8 @@ Status BuildTable(
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const std::string* full_history_ts_low = nullptr,
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
Version* version = nullptr, uint64_t* num_input_entries = nullptr,
|
||||
uint64_t* memtable_payload_bytes = nullptr,
|
||||
uint64_t* memtable_garbage_bytes = nullptr);
|
||||
Version* version = nullptr, uint64_t* memtable_payload_bytes = nullptr,
|
||||
uint64_t* memtable_garbage_bytes = nullptr,
|
||||
InternalStats::CompactionStats* flush_stats = nullptr);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -24,12 +24,14 @@
|
||||
#include "rocksdb/experimental.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table.h"
|
||||
@@ -49,6 +51,7 @@
|
||||
#include "util/stderr_logger.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
using ROCKSDB_NAMESPACE::BackgroundErrorReason;
|
||||
using ROCKSDB_NAMESPACE::BackupEngine;
|
||||
using ROCKSDB_NAMESPACE::BackupEngineOptions;
|
||||
using ROCKSDB_NAMESPACE::BackupID;
|
||||
@@ -65,7 +68,9 @@ using ROCKSDB_NAMESPACE::ColumnFamilyMetaData;
|
||||
using ROCKSDB_NAMESPACE::ColumnFamilyOptions;
|
||||
using ROCKSDB_NAMESPACE::CompactionFilter;
|
||||
using ROCKSDB_NAMESPACE::CompactionFilterFactory;
|
||||
using ROCKSDB_NAMESPACE::CompactionJobInfo;
|
||||
using ROCKSDB_NAMESPACE::CompactionOptionsFIFO;
|
||||
using ROCKSDB_NAMESPACE::CompactionReason;
|
||||
using ROCKSDB_NAMESPACE::CompactRangeOptions;
|
||||
using ROCKSDB_NAMESPACE::Comparator;
|
||||
using ROCKSDB_NAMESPACE::CompressionType;
|
||||
@@ -76,11 +81,16 @@ using ROCKSDB_NAMESPACE::DBOptions;
|
||||
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;
|
||||
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;
|
||||
@@ -90,6 +100,7 @@ using ROCKSDB_NAMESPACE::Logger;
|
||||
using ROCKSDB_NAMESPACE::LRUCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::MemoryAllocator;
|
||||
using ROCKSDB_NAMESPACE::MemoryUtil;
|
||||
using ROCKSDB_NAMESPACE::MemTableInfo;
|
||||
using ROCKSDB_NAMESPACE::MergeOperator;
|
||||
using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::NewCompactOnDeletionCollectorFactory;
|
||||
@@ -113,10 +124,12 @@ using ROCKSDB_NAMESPACE::Slice;
|
||||
using ROCKSDB_NAMESPACE::SliceParts;
|
||||
using ROCKSDB_NAMESPACE::SliceTransform;
|
||||
using ROCKSDB_NAMESPACE::Snapshot;
|
||||
using ROCKSDB_NAMESPACE::SstFileManager;
|
||||
using ROCKSDB_NAMESPACE::SstFileMetaData;
|
||||
using ROCKSDB_NAMESPACE::SstFileWriter;
|
||||
using ROCKSDB_NAMESPACE::Status;
|
||||
using ROCKSDB_NAMESPACE::StderrLogger;
|
||||
using ROCKSDB_NAMESPACE::SubcompactionJobInfo;
|
||||
using ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory;
|
||||
using ROCKSDB_NAMESPACE::Transaction;
|
||||
using ROCKSDB_NAMESPACE::TransactionDB;
|
||||
@@ -130,6 +143,8 @@ using ROCKSDB_NAMESPACE::WriteBatch;
|
||||
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
|
||||
using ROCKSDB_NAMESPACE::WriteBufferManager;
|
||||
using ROCKSDB_NAMESPACE::WriteOptions;
|
||||
using ROCKSDB_NAMESPACE::WriteStallCondition;
|
||||
using ROCKSDB_NAMESPACE::WriteStallInfo;
|
||||
|
||||
using std::unordered_set;
|
||||
using std::vector;
|
||||
@@ -139,6 +154,9 @@ extern "C" {
|
||||
struct rocksdb_t {
|
||||
DB* rep;
|
||||
};
|
||||
struct rocksdb_status_ptr_t {
|
||||
Status* rep;
|
||||
};
|
||||
struct rocksdb_backup_engine_t {
|
||||
BackupEngine* rep;
|
||||
};
|
||||
@@ -226,6 +244,12 @@ struct rocksdb_cache_t {
|
||||
struct rocksdb_write_buffer_manager_t {
|
||||
std::shared_ptr<WriteBufferManager> rep;
|
||||
};
|
||||
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;
|
||||
};
|
||||
@@ -236,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;
|
||||
};
|
||||
@@ -292,6 +322,28 @@ struct rocksdb_compactionfiltercontext_t {
|
||||
CompactionFilter::Context rep;
|
||||
};
|
||||
|
||||
struct rocksdb_flushjobinfo_t {
|
||||
FlushJobInfo rep;
|
||||
};
|
||||
struct rocksdb_writestallcondition_t {
|
||||
WriteStallCondition rep;
|
||||
};
|
||||
struct rocksdb_writestallinfo_t {
|
||||
WriteStallInfo rep;
|
||||
};
|
||||
struct rocksdb_memtableinfo_t {
|
||||
MemTableInfo rep;
|
||||
};
|
||||
struct rocksdb_compactionjobinfo_t {
|
||||
CompactionJobInfo rep;
|
||||
};
|
||||
struct rocksdb_subcompactionjobinfo_t {
|
||||
SubcompactionJobInfo rep;
|
||||
};
|
||||
struct rocksdb_externalfileingestioninfo_t {
|
||||
ExternalFileIngestionInfo rep;
|
||||
};
|
||||
|
||||
struct rocksdb_statistics_histogram_data_t {
|
||||
rocksdb_statistics_histogram_data_t() : rep() {}
|
||||
HistogramData rep;
|
||||
@@ -884,6 +936,10 @@ void rocksdb_backup_engine_options_destroy(
|
||||
delete options;
|
||||
}
|
||||
|
||||
void rocksdb_status_ptr_get_error(rocksdb_status_ptr_t* status, char** errptr) {
|
||||
SaveError(errptr, *(status->rep));
|
||||
}
|
||||
|
||||
rocksdb_checkpoint_t* rocksdb_checkpoint_object_create(rocksdb_t* db,
|
||||
char** errptr) {
|
||||
Checkpoint* checkpoint;
|
||||
@@ -902,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;
|
||||
@@ -1145,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);
|
||||
@@ -2627,6 +2719,16 @@ rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_readopts(
|
||||
rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator,
|
||||
const rocksdb_readoptions_t* options) {
|
||||
rocksdb_iterator_t* result = new rocksdb_iterator_t;
|
||||
result->rep =
|
||||
wbwi->rep->NewIteratorWithBase(base_iterator->rep, &options->rep);
|
||||
delete base_iterator;
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf(
|
||||
rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator,
|
||||
rocksdb_column_family_handle_t* column_family) {
|
||||
@@ -2637,6 +2739,17 @@ rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* rocksdb_writebatch_wi_create_iterator_with_base_cf_readopts(
|
||||
rocksdb_writebatch_wi_t* wbwi, rocksdb_iterator_t* base_iterator,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const rocksdb_readoptions_t* options) {
|
||||
rocksdb_iterator_t* result = new rocksdb_iterator_t;
|
||||
result->rep = wbwi->rep->NewIteratorWithBase(
|
||||
column_family->rep, base_iterator->rep, &options->rep);
|
||||
delete base_iterator;
|
||||
return result;
|
||||
}
|
||||
|
||||
char* rocksdb_writebatch_wi_get_from_batch(rocksdb_writebatch_wi_t* wbwi,
|
||||
const rocksdb_options_t* options,
|
||||
const char* key, size_t keylen,
|
||||
@@ -2696,6 +2809,23 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_pinnableslice_t* rocksdb_writebatch_wi_get_pinned_from_batch_and_db(
|
||||
rocksdb_writebatch_wi_t* wbwi, rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options, const char* key, size_t keylen,
|
||||
char** errptr) {
|
||||
rocksdb_pinnableslice_t* v = new (rocksdb_pinnableslice_t);
|
||||
Status s = wbwi->rep->GetFromBatchAndDB(db->rep, options->rep,
|
||||
Slice(key, keylen), &v->rep);
|
||||
if (!s.ok()) {
|
||||
delete (v);
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
|
||||
rocksdb_writebatch_wi_t* wbwi, rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
@@ -2717,6 +2847,24 @@ char* rocksdb_writebatch_wi_get_from_batch_and_db_cf(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_pinnableslice_t* rocksdb_writebatch_wi_get_pinned_from_batch_and_db_cf(
|
||||
rocksdb_writebatch_wi_t* wbwi, rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
size_t keylen, char** errptr) {
|
||||
rocksdb_pinnableslice_t* v = new (rocksdb_pinnableslice_t);
|
||||
Status s = wbwi->rep->GetFromBatchAndDB(
|
||||
db->rep, options->rep, column_family->rep, Slice(key, keylen), &v->rep);
|
||||
if (!s.ok()) {
|
||||
delete (v);
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
void rocksdb_write_writebatch_wi(rocksdb_t* db,
|
||||
const rocksdb_writeoptions_t* options,
|
||||
rocksdb_writebatch_wi_t* wbwi, char** errptr) {
|
||||
@@ -2930,6 +3078,361 @@ void rocksdb_block_based_options_set_unpartitioned_pinning_tier(
|
||||
static_cast<ROCKSDB_NAMESPACE::PinningTier>(v);
|
||||
}
|
||||
|
||||
/* FlushJobInfo */
|
||||
|
||||
const char* rocksdb_flushjobinfo_cf_name(const rocksdb_flushjobinfo_t* info,
|
||||
size_t* size) {
|
||||
*size = info->rep.cf_name.size();
|
||||
return info->rep.cf_name.data();
|
||||
}
|
||||
|
||||
const char* rocksdb_flushjobinfo_file_path(const rocksdb_flushjobinfo_t* info,
|
||||
size_t* size) {
|
||||
*size = info->rep.file_path.size();
|
||||
return info->rep.file_path.data();
|
||||
}
|
||||
|
||||
unsigned char rocksdb_flushjobinfo_triggered_writes_slowdown(
|
||||
const rocksdb_flushjobinfo_t* info) {
|
||||
return info->rep.triggered_writes_slowdown;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_flushjobinfo_triggered_writes_stop(
|
||||
const rocksdb_flushjobinfo_t* info) {
|
||||
return info->rep.triggered_writes_stop;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_flushjobinfo_largest_seqno(
|
||||
const rocksdb_flushjobinfo_t* info) {
|
||||
return info->rep.largest_seqno;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_flushjobinfo_smallest_seqno(
|
||||
const rocksdb_flushjobinfo_t* info) {
|
||||
return info->rep.smallest_seqno;
|
||||
}
|
||||
|
||||
uint32_t rocksdb_flushjobinfo_flush_reason(const rocksdb_flushjobinfo_t* info) {
|
||||
return static_cast<uint32_t>(info->rep.flush_reason);
|
||||
}
|
||||
|
||||
void rocksdb_reset_status(rocksdb_status_ptr_t* status_ptr) {
|
||||
auto ptr = status_ptr->rep;
|
||||
*ptr = Status::OK();
|
||||
}
|
||||
|
||||
/* CompactionJobInfo */
|
||||
|
||||
void rocksdb_compactionjobinfo_status(const rocksdb_compactionjobinfo_t* info,
|
||||
char** errptr) {
|
||||
SaveError(errptr, info->rep.status);
|
||||
}
|
||||
|
||||
const char* rocksdb_compactionjobinfo_cf_name(
|
||||
const rocksdb_compactionjobinfo_t* info, size_t* size) {
|
||||
*size = info->rep.cf_name.size();
|
||||
return info->rep.cf_name.data();
|
||||
}
|
||||
|
||||
size_t rocksdb_compactionjobinfo_input_files_count(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.input_files.size();
|
||||
}
|
||||
|
||||
const char* rocksdb_compactionjobinfo_input_file_at(
|
||||
const rocksdb_compactionjobinfo_t* info, size_t pos, size_t* size) {
|
||||
assert(info != nullptr);
|
||||
assert(pos < info->rep.input_files.size());
|
||||
|
||||
const std::string& path = info->rep.input_files[pos];
|
||||
*size = path.size();
|
||||
return path.data();
|
||||
}
|
||||
|
||||
size_t rocksdb_compactionjobinfo_output_files_count(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.output_files.size();
|
||||
}
|
||||
|
||||
const char* rocksdb_compactionjobinfo_output_file_at(
|
||||
const rocksdb_compactionjobinfo_t* info, size_t pos, size_t* size) {
|
||||
assert(info != nullptr);
|
||||
assert(pos < info->rep.output_files.size());
|
||||
|
||||
const std::string& path = info->rep.output_files[pos];
|
||||
*size = path.size();
|
||||
return path.data();
|
||||
}
|
||||
|
||||
uint64_t rocksdb_compactionjobinfo_elapsed_micros(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.stats.elapsed_micros;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_compactionjobinfo_num_corrupt_keys(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.stats.num_corrupt_keys;
|
||||
}
|
||||
|
||||
int rocksdb_compactionjobinfo_base_input_level(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.base_input_level;
|
||||
}
|
||||
|
||||
int rocksdb_compactionjobinfo_output_level(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.output_level;
|
||||
}
|
||||
|
||||
size_t rocksdb_compactionjobinfo_num_input_files(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.stats.num_input_files;
|
||||
}
|
||||
|
||||
size_t rocksdb_compactionjobinfo_num_input_files_at_output_level(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.stats.num_input_files_at_output_level;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_compactionjobinfo_input_records(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.stats.num_input_records;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_compactionjobinfo_output_records(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.stats.num_output_records;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_compactionjobinfo_total_input_bytes(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.stats.total_input_bytes;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_compactionjobinfo_total_output_bytes(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return info->rep.stats.total_output_bytes;
|
||||
}
|
||||
|
||||
uint32_t rocksdb_compactionjobinfo_compaction_reason(
|
||||
const rocksdb_compactionjobinfo_t* info) {
|
||||
return static_cast<uint32_t>(info->rep.compaction_reason);
|
||||
}
|
||||
|
||||
/* SubcompactionJobInfo */
|
||||
|
||||
void rocksdb_subcompactionjobinfo_status(
|
||||
const rocksdb_subcompactionjobinfo_t* info, char** errptr) {
|
||||
SaveError(errptr, info->rep.status);
|
||||
}
|
||||
|
||||
const char* rocksdb_subcompactionjobinfo_cf_name(
|
||||
const rocksdb_subcompactionjobinfo_t* info, size_t* size) {
|
||||
*size = info->rep.cf_name.size();
|
||||
return info->rep.cf_name.data();
|
||||
}
|
||||
|
||||
uint64_t rocksdb_subcompactionjobinfo_thread_id(
|
||||
const rocksdb_subcompactionjobinfo_t* info) {
|
||||
return info->rep.thread_id;
|
||||
}
|
||||
|
||||
int rocksdb_subcompactionjobinfo_base_input_level(
|
||||
const rocksdb_subcompactionjobinfo_t* info) {
|
||||
return info->rep.base_input_level;
|
||||
}
|
||||
|
||||
int rocksdb_subcompactionjobinfo_output_level(
|
||||
const rocksdb_subcompactionjobinfo_t* info) {
|
||||
return info->rep.output_level;
|
||||
}
|
||||
|
||||
uint32_t rocksdb_subcompactionjobinfo_compaction_reason(
|
||||
const rocksdb_subcompactionjobinfo_t* info) {
|
||||
return static_cast<uint32_t>(info->rep.compaction_reason);
|
||||
}
|
||||
|
||||
/* ExternalFileIngestionInfo */
|
||||
|
||||
const char* rocksdb_externalfileingestioninfo_cf_name(
|
||||
const rocksdb_externalfileingestioninfo_t* info, size_t* size) {
|
||||
*size = info->rep.cf_name.size();
|
||||
return info->rep.cf_name.data();
|
||||
}
|
||||
|
||||
const char* rocksdb_externalfileingestioninfo_internal_file_path(
|
||||
const rocksdb_externalfileingestioninfo_t* info, size_t* size) {
|
||||
*size = info->rep.internal_file_path.size();
|
||||
return info->rep.internal_file_path.data();
|
||||
}
|
||||
|
||||
/* External write stall info */
|
||||
extern ROCKSDB_LIBRARY_API const char* rocksdb_writestallinfo_cf_name(
|
||||
const rocksdb_writestallinfo_t* info, size_t* size) {
|
||||
*size = info->rep.cf_name.size();
|
||||
return info->rep.cf_name.data();
|
||||
}
|
||||
|
||||
const rocksdb_writestallcondition_t* rocksdb_writestallinfo_cur(
|
||||
const rocksdb_writestallinfo_t* info) {
|
||||
return reinterpret_cast<const rocksdb_writestallcondition_t*>(
|
||||
&info->rep.condition.cur);
|
||||
}
|
||||
|
||||
const rocksdb_writestallcondition_t* rocksdb_writestallinfo_prev(
|
||||
const rocksdb_writestallinfo_t* info) {
|
||||
return reinterpret_cast<const rocksdb_writestallcondition_t*>(
|
||||
&info->rep.condition.prev);
|
||||
}
|
||||
|
||||
const char* rocksdb_memtableinfo_cf_name(const rocksdb_memtableinfo_t* info,
|
||||
size_t* size) {
|
||||
*size = info->rep.cf_name.size();
|
||||
return info->rep.cf_name.data();
|
||||
}
|
||||
|
||||
uint64_t rocksdb_memtableinfo_first_seqno(const rocksdb_memtableinfo_t* info) {
|
||||
return info->rep.first_seqno;
|
||||
}
|
||||
uint64_t rocksdb_memtableinfo_earliest_seqno(
|
||||
const rocksdb_memtableinfo_t* info) {
|
||||
return info->rep.earliest_seqno;
|
||||
}
|
||||
uint64_t rocksdb_memtableinfo_num_entries(const rocksdb_memtableinfo_t* info) {
|
||||
return info->rep.num_entries;
|
||||
}
|
||||
uint64_t rocksdb_memtableinfo_num_deletes(const rocksdb_memtableinfo_t* info) {
|
||||
return info->rep.num_deletes;
|
||||
}
|
||||
|
||||
/* event listener */
|
||||
|
||||
struct rocksdb_eventlistener_t : public EventListener {
|
||||
void* state_{};
|
||||
void (*destructor_)(void*){};
|
||||
void (*on_flush_begin)(void*, rocksdb_t*, const rocksdb_flushjobinfo_t*){};
|
||||
void (*on_flush_completed)(void*, rocksdb_t*,
|
||||
const rocksdb_flushjobinfo_t*){};
|
||||
void (*on_compaction_begin)(void*, rocksdb_t*,
|
||||
const rocksdb_compactionjobinfo_t*){};
|
||||
void (*on_compaction_completed)(void*, rocksdb_t*,
|
||||
const rocksdb_compactionjobinfo_t*){};
|
||||
void (*on_subcompaction_begin)(void*,
|
||||
const rocksdb_subcompactionjobinfo_t*){};
|
||||
void (*on_subcompaction_completed)(void*,
|
||||
const rocksdb_subcompactionjobinfo_t*){};
|
||||
void (*on_external_file_ingested)(
|
||||
void*, rocksdb_t*, const rocksdb_externalfileingestioninfo_t*){};
|
||||
void (*on_background_error)(void*, uint32_t, rocksdb_status_ptr_t*){};
|
||||
void (*on_stall_conditions_changed)(void*, const rocksdb_writestallinfo_t*){};
|
||||
void (*on_memtable_sealed)(void*, const rocksdb_memtableinfo_t*){};
|
||||
|
||||
rocksdb_eventlistener_t() = default;
|
||||
|
||||
rocksdb_eventlistener_t(const rocksdb_eventlistener_t&) = delete;
|
||||
rocksdb_eventlistener_t& operator=(const rocksdb_eventlistener_t&) = delete;
|
||||
rocksdb_eventlistener_t(rocksdb_eventlistener_t&&) = delete;
|
||||
rocksdb_eventlistener_t& operator=(rocksdb_eventlistener_t&&) = delete;
|
||||
|
||||
void OnFlushBegin(DB* db, const FlushJobInfo& info) override {
|
||||
rocksdb_t c_db = {db};
|
||||
on_flush_begin(state_, &c_db,
|
||||
reinterpret_cast<const rocksdb_flushjobinfo_t*>(&info));
|
||||
}
|
||||
|
||||
void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
|
||||
rocksdb_t c_db = {db};
|
||||
on_flush_completed(state_, &c_db,
|
||||
reinterpret_cast<const rocksdb_flushjobinfo_t*>(&info));
|
||||
}
|
||||
|
||||
void OnCompactionBegin(DB* db, const CompactionJobInfo& info) override {
|
||||
rocksdb_t c_db = {db};
|
||||
on_compaction_begin(
|
||||
state_, &c_db,
|
||||
reinterpret_cast<const rocksdb_compactionjobinfo_t*>(&info));
|
||||
}
|
||||
|
||||
void OnCompactionCompleted(DB* db, const CompactionJobInfo& info) override {
|
||||
rocksdb_t c_db = {db};
|
||||
on_compaction_completed(
|
||||
state_, &c_db,
|
||||
reinterpret_cast<const rocksdb_compactionjobinfo_t*>(&info));
|
||||
}
|
||||
|
||||
void OnSubcompactionBegin(const SubcompactionJobInfo& info) override {
|
||||
on_subcompaction_begin(
|
||||
state_, reinterpret_cast<const rocksdb_subcompactionjobinfo_t*>(&info));
|
||||
}
|
||||
|
||||
void OnSubcompactionCompleted(const SubcompactionJobInfo& info) override {
|
||||
on_subcompaction_completed(
|
||||
state_, reinterpret_cast<const rocksdb_subcompactionjobinfo_t*>(&info));
|
||||
}
|
||||
|
||||
void OnExternalFileIngested(DB* db,
|
||||
const ExternalFileIngestionInfo& info) override {
|
||||
rocksdb_t c_db = {db};
|
||||
on_external_file_ingested(
|
||||
state_, &c_db,
|
||||
reinterpret_cast<const rocksdb_externalfileingestioninfo_t*>(&info));
|
||||
}
|
||||
|
||||
void OnBackgroundError(BackgroundErrorReason reason,
|
||||
Status* status) override {
|
||||
rocksdb_status_ptr_t* s = new rocksdb_status_ptr_t;
|
||||
s->rep = status;
|
||||
on_background_error(state_, static_cast<uint32_t>(reason), s);
|
||||
delete s;
|
||||
}
|
||||
|
||||
void OnStallConditionsChanged(const WriteStallInfo& info) override {
|
||||
on_stall_conditions_changed(
|
||||
state_, reinterpret_cast<const rocksdb_writestallinfo_t*>(&info));
|
||||
}
|
||||
|
||||
void OnMemTableSealed(const MemTableInfo& info) override {
|
||||
on_memtable_sealed(state_,
|
||||
reinterpret_cast<const rocksdb_memtableinfo_t*>(&info));
|
||||
}
|
||||
|
||||
~rocksdb_eventlistener_t() override { destructor_(state_); }
|
||||
};
|
||||
|
||||
rocksdb_eventlistener_t* rocksdb_eventlistener_create(
|
||||
void* state_, void (*destructor_)(void*), on_flush_begin_cb on_flush_begin,
|
||||
on_flush_completed_cb on_flush_completed,
|
||||
on_compaction_begin_cb on_compaction_begin,
|
||||
on_compaction_completed_cb on_compaction_completed,
|
||||
on_subcompaction_begin_cb on_subcompaction_begin,
|
||||
on_subcompaction_completed_cb on_subcompaction_completed,
|
||||
on_external_file_ingested_cb on_external_file_ingested,
|
||||
on_background_error_cb on_background_error,
|
||||
on_stall_conditions_changed_cb on_stall_conditions_changed,
|
||||
on_memtable_sealed_cb on_memtable_sealed) {
|
||||
rocksdb_eventlistener_t* et = new rocksdb_eventlistener_t;
|
||||
et->state_ = state_;
|
||||
et->destructor_ = destructor_;
|
||||
et->on_flush_begin = on_flush_begin;
|
||||
et->on_flush_completed = on_flush_completed;
|
||||
et->on_compaction_begin = on_compaction_begin;
|
||||
et->on_compaction_completed = on_compaction_completed;
|
||||
et->on_subcompaction_begin = on_subcompaction_begin;
|
||||
et->on_subcompaction_completed = on_subcompaction_completed;
|
||||
et->on_external_file_ingested = on_external_file_ingested;
|
||||
et->on_background_error = on_background_error;
|
||||
et->on_stall_conditions_changed = on_stall_conditions_changed;
|
||||
et->on_memtable_sealed = on_memtable_sealed;
|
||||
return et;
|
||||
}
|
||||
|
||||
void rocksdb_eventlistener_destroy(rocksdb_eventlistener_t* t) { delete t; }
|
||||
|
||||
void rocksdb_options_add_eventlistener(rocksdb_options_t* opt,
|
||||
rocksdb_eventlistener_t* t) {
|
||||
opt->rep.listeners.emplace_back(std::shared_ptr<EventListener>(t));
|
||||
}
|
||||
|
||||
rocksdb_cuckoo_table_options_t* rocksdb_cuckoo_options_create() {
|
||||
return new rocksdb_cuckoo_table_options_t;
|
||||
}
|
||||
@@ -3183,6 +3686,11 @@ void rocksdb_options_set_write_buffer_manager(
|
||||
opt->rep.write_buffer_manager = wbm->rep;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_sst_file_manager(rocksdb_options_t* opt,
|
||||
rocksdb_sst_file_manager_t* sfm) {
|
||||
opt->rep.sst_file_manager = sfm->rep;
|
||||
}
|
||||
|
||||
size_t rocksdb_options_get_write_buffer_size(rocksdb_options_t* opt) {
|
||||
return opt->rep.write_buffer_size;
|
||||
}
|
||||
@@ -3295,6 +3803,26 @@ uint64_t rocksdb_options_get_periodic_compaction_seconds(
|
||||
return opt->rep.periodic_compaction_seconds;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_op_scan_flush_trigger(rocksdb_options_t* opt,
|
||||
uint32_t n) {
|
||||
opt->rep.memtable_op_scan_flush_trigger = n;
|
||||
}
|
||||
|
||||
uint32_t rocksdb_options_get_memtable_op_scan_flush_trigger(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.memtable_op_scan_flush_trigger;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_avg_op_scan_flush_trigger(
|
||||
rocksdb_options_t* opt, uint32_t n) {
|
||||
opt->rep.memtable_avg_op_scan_flush_trigger = n;
|
||||
}
|
||||
|
||||
uint32_t rocksdb_options_get_memtable_avg_op_scan_flush_trigger(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.memtable_avg_op_scan_flush_trigger;
|
||||
}
|
||||
|
||||
void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
|
||||
opt->rep.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
}
|
||||
@@ -3804,16 +4332,6 @@ int rocksdb_options_get_min_write_buffer_number_to_merge(
|
||||
return opt->rep.min_write_buffer_number_to_merge;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_write_buffer_number_to_maintain(
|
||||
rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_write_buffer_number_to_maintain = n;
|
||||
}
|
||||
|
||||
int rocksdb_options_get_max_write_buffer_number_to_maintain(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.max_write_buffer_number_to_maintain;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_write_buffer_size_to_maintain(
|
||||
rocksdb_options_t* opt, int64_t n) {
|
||||
opt->rep.max_write_buffer_size_to_maintain = n;
|
||||
@@ -4280,6 +4798,15 @@ void rocksdb_options_add_compact_on_deletion_collector_factory_del_ratio(
|
||||
opt->rep.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
}
|
||||
|
||||
void rocksdb_options_add_compact_on_deletion_collector_factory_min_file_size(
|
||||
rocksdb_options_t* opt, size_t window_size, size_t num_dels_trigger,
|
||||
double deletion_ratio, uint64_t min_file_size) {
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory>
|
||||
compact_on_del = NewCompactOnDeletionCollectorFactory(
|
||||
window_size, num_dels_trigger, deletion_ratio, min_file_size);
|
||||
opt->rep.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
}
|
||||
|
||||
void rocksdb_set_perf_level(int v) {
|
||||
PerfLevel level = static_cast<PerfLevel>(v);
|
||||
SetPerfLevel(level);
|
||||
@@ -4332,6 +4859,8 @@ uint64_t rocksdb_perfcontext_metric(rocksdb_perfcontext_t* context,
|
||||
return rep->internal_recent_skipped_count;
|
||||
case rocksdb_internal_merge_count:
|
||||
return rep->internal_merge_count;
|
||||
case rocksdb_internal_merge_point_lookup_count:
|
||||
return rep->internal_merge_point_lookup_count;
|
||||
case rocksdb_get_snapshot_time:
|
||||
return rep->get_snapshot_time;
|
||||
case rocksdb_get_from_memtable_time:
|
||||
@@ -5239,6 +5768,67 @@ ROCKSDB_LIBRARY_API void rocksdb_write_buffer_manager_set_allow_stall(
|
||||
wbm->rep->SetAllowStall(new_allow_stall);
|
||||
}
|
||||
|
||||
rocksdb_sst_file_manager_t* rocksdb_sst_file_manager_create(
|
||||
rocksdb_env_t* env) {
|
||||
rocksdb_sst_file_manager_t* sfm = new rocksdb_sst_file_manager_t;
|
||||
sfm->rep.reset(ROCKSDB_NAMESPACE::NewSstFileManager(env->rep));
|
||||
return sfm;
|
||||
}
|
||||
|
||||
void rocksdb_sst_file_manager_destroy(rocksdb_sst_file_manager_t* sfm) {
|
||||
delete sfm;
|
||||
}
|
||||
|
||||
void rocksdb_sst_file_manager_set_max_allowed_space_usage(
|
||||
rocksdb_sst_file_manager_t* sfm, uint64_t max_allowed_space) {
|
||||
sfm->rep->SetMaxAllowedSpaceUsage(max_allowed_space);
|
||||
}
|
||||
|
||||
void rocksdb_sst_file_manager_set_compaction_buffer_size(
|
||||
rocksdb_sst_file_manager_t* sfm, uint64_t compaction_buffer_size) {
|
||||
sfm->rep->SetCompactionBufferSize(compaction_buffer_size);
|
||||
}
|
||||
|
||||
bool rocksdb_sst_file_manager_is_max_allowed_space_reached(
|
||||
rocksdb_sst_file_manager_t* sfm) {
|
||||
return sfm->rep->IsMaxAllowedSpaceReached();
|
||||
}
|
||||
|
||||
bool rocksdb_sst_file_manager_is_max_allowed_space_reached_including_compactions(
|
||||
rocksdb_sst_file_manager_t* sfm) {
|
||||
return sfm->rep->IsMaxAllowedSpaceReachedIncludingCompactions();
|
||||
}
|
||||
|
||||
uint64_t rocksdb_sst_file_manager_get_total_size(
|
||||
rocksdb_sst_file_manager_t* sfm) {
|
||||
return sfm->rep->GetTotalSize();
|
||||
}
|
||||
|
||||
int64_t rocksdb_sst_file_manager_get_delete_rate_bytes_per_second(
|
||||
rocksdb_sst_file_manager_t* sfm) {
|
||||
return sfm->rep->GetDeleteRateBytesPerSecond();
|
||||
}
|
||||
|
||||
void rocksdb_sst_file_manager_set_delete_rate_bytes_per_second(
|
||||
rocksdb_sst_file_manager_t* sfm, int64_t delete_rate) {
|
||||
return sfm->rep->SetDeleteRateBytesPerSecond(delete_rate);
|
||||
}
|
||||
|
||||
double rocksdb_sst_file_manager_get_max_trash_db_ratio(
|
||||
rocksdb_sst_file_manager_t* sfm) {
|
||||
return sfm->rep->GetMaxTrashDBRatio();
|
||||
}
|
||||
|
||||
void rocksdb_sst_file_manager_set_max_trash_db_ratio(
|
||||
rocksdb_sst_file_manager_t* sfm, double ratio) {
|
||||
return sfm->rep->SetMaxTrashDBRatio(ratio);
|
||||
}
|
||||
|
||||
uint64_t rocksdb_sst_file_manager_get_total_trash_size(
|
||||
rocksdb_sst_file_manager_t* sfm) {
|
||||
return sfm->rep->GetTotalTrashSize();
|
||||
}
|
||||
|
||||
rocksdb_dbpath_t* rocksdb_dbpath_create(const char* path,
|
||||
uint64_t target_size) {
|
||||
rocksdb_dbpath_t* result = new rocksdb_dbpath_t;
|
||||
@@ -5666,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());
|
||||
}
|
||||
@@ -5679,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;
|
||||
}
|
||||
@@ -5699,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;
|
||||
}
|
||||
@@ -5709,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,
|
||||
@@ -5859,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() {
|
||||
@@ -5890,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;
|
||||
}
|
||||
|
||||
+196
-24
@@ -103,6 +103,12 @@ static void CheckValue(char* err, const char* expected, char** actual,
|
||||
Free(actual);
|
||||
}
|
||||
|
||||
static void CheckPinnedValue(char* err, const char* expected,
|
||||
const char** actual, size_t actual_length) {
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, *actual, actual_length);
|
||||
}
|
||||
|
||||
static void CheckGet(rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
const char* key, const char* expected) {
|
||||
char* err = NULL;
|
||||
@@ -1030,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");
|
||||
@@ -1245,6 +1323,8 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(count == 3);
|
||||
size_t size;
|
||||
char* value;
|
||||
const char* pinned_value;
|
||||
rocksdb_pinnableslice_t* p;
|
||||
value = rocksdb_writebatch_wi_get_from_batch(wbi, options, "box", 3, &size,
|
||||
&err);
|
||||
CheckValue(err, "c", &value, size);
|
||||
@@ -1254,9 +1334,19 @@ int main(int argc, char** argv) {
|
||||
value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions,
|
||||
"foo", 3, &size, &err);
|
||||
CheckValue(err, "hello", &value, size);
|
||||
p = rocksdb_writebatch_wi_get_pinned_from_batch_and_db(wbi, db, roptions,
|
||||
"foo", 3, &err);
|
||||
pinned_value = rocksdb_pinnableslice_value(p, &size);
|
||||
CheckPinnedValue(err, "hello", &pinned_value, size);
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions,
|
||||
"box", 3, &size, &err);
|
||||
CheckValue(err, "c", &value, size);
|
||||
p = rocksdb_writebatch_wi_get_pinned_from_batch_and_db(wbi, db, roptions,
|
||||
"box", 3, &err);
|
||||
pinned_value = rocksdb_pinnableslice_value(p, &size);
|
||||
CheckPinnedValue(err, "c", &pinned_value, size);
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
rocksdb_write_writebatch_wi(db, woptions, wbi, &err);
|
||||
CheckNoError(err);
|
||||
CheckGet(db, roptions, "foo", "hello");
|
||||
@@ -1362,6 +1452,46 @@ int main(int argc, char** argv) {
|
||||
rocksdb_writebatch_wi_destroy(wbi);
|
||||
}
|
||||
|
||||
StartPhase("wbwi_iter_readoptions");
|
||||
{
|
||||
rocksdb_readoptions_t* iter_roptions = rocksdb_readoptions_create();
|
||||
rocksdb_readoptions_set_iterate_lower_bound(iter_roptions, "boy", 3);
|
||||
rocksdb_readoptions_set_iterate_upper_bound(iter_roptions, "fool", 4);
|
||||
rocksdb_iterator_t* base_iter = rocksdb_create_iterator(db, iter_roptions);
|
||||
rocksdb_writebatch_wi_t* wbi = rocksdb_writebatch_wi_create(0, 1);
|
||||
rocksdb_writebatch_wi_put(wbi, "bar", 3, "b",
|
||||
1); // should get filtered out
|
||||
rocksdb_writebatch_wi_put(wbi, "cat", 3, "miau", 4);
|
||||
rocksdb_writebatch_wi_put(wbi, "gnu", 3, "muh",
|
||||
3); // should get filtered out
|
||||
rocksdb_iterator_t* iter =
|
||||
rocksdb_writebatch_wi_create_iterator_with_base_readopts(wbi, base_iter,
|
||||
iter_roptions);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "cat", "miau");
|
||||
rocksdb_iter_next(iter);
|
||||
CheckIter(iter, "foo", "hello");
|
||||
rocksdb_iter_prev(iter);
|
||||
CheckIter(iter, "cat", "miau");
|
||||
rocksdb_iter_prev(iter);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_last(iter);
|
||||
CheckIter(iter, "foo", "hello");
|
||||
rocksdb_iter_seek(iter, "b", 1);
|
||||
CheckIter(iter, "cat", "miau");
|
||||
rocksdb_iter_seek_for_prev(iter, "d", 1);
|
||||
CheckIter(iter, "cat", "miau");
|
||||
rocksdb_iter_seek_for_prev(iter, "fool", 3);
|
||||
CheckIter(iter, "foo", "hello");
|
||||
rocksdb_iter_get_error(iter, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
rocksdb_writebatch_wi_destroy(wbi);
|
||||
rocksdb_readoptions_destroy(iter_roptions);
|
||||
}
|
||||
|
||||
StartPhase("multiget");
|
||||
{
|
||||
const char* keys[3] = {"box", "foo", "notfound"};
|
||||
@@ -1792,6 +1922,35 @@ int main(int argc, char** argv) {
|
||||
rocksdb_flush_wal(db, 1, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// Test WriteBatchWithIndex iteration with Column Family
|
||||
rocksdb_writebatch_wi_t* wbwi = rocksdb_writebatch_wi_create(0, true);
|
||||
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "boat", 4, "row",
|
||||
3); // should be filtered out
|
||||
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "buffy", 5, "charmed", 7);
|
||||
rocksdb_writebatch_wi_put_cf(wbwi, handles[1], "bus", 3, "yellow",
|
||||
6); // should be filtered out
|
||||
rocksdb_readoptions_t* iter_roptions = rocksdb_readoptions_create();
|
||||
rocksdb_readoptions_set_iterate_lower_bound(iter_roptions, "bu", 2);
|
||||
rocksdb_readoptions_set_iterate_upper_bound(iter_roptions, "buffz", 5);
|
||||
rocksdb_iterator_t* base_iter =
|
||||
rocksdb_create_iterator_cf(db, iter_roptions, handles[1]);
|
||||
rocksdb_iterator_t* wbwi_iter =
|
||||
rocksdb_writebatch_wi_create_iterator_with_base_cf_readopts(
|
||||
wbwi, base_iter, handles[1], iter_roptions);
|
||||
|
||||
CheckCondition(!rocksdb_iter_valid(wbwi_iter));
|
||||
rocksdb_iter_seek_to_first(wbwi_iter);
|
||||
CheckCondition(rocksdb_iter_valid(wbwi_iter));
|
||||
CheckIter(wbwi_iter, "buff", "rocksdb");
|
||||
rocksdb_iter_next(wbwi_iter);
|
||||
CheckIter(wbwi_iter, "buffy", "charmed");
|
||||
rocksdb_iter_next(wbwi_iter);
|
||||
CheckCondition(!rocksdb_iter_valid(wbwi_iter));
|
||||
|
||||
rocksdb_iter_destroy(wbwi_iter);
|
||||
rocksdb_writebatch_wi_destroy(wbwi);
|
||||
rocksdb_readoptions_destroy(iter_roptions);
|
||||
|
||||
const char* keys[3] = {"box", "box", "barfooxx"};
|
||||
const rocksdb_column_family_handle_t* get_handles[3] = {
|
||||
handles[0], handles[1], handles[1]};
|
||||
@@ -2129,16 +2288,20 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(100000 ==
|
||||
rocksdb_options_get_periodic_compaction_seconds(o));
|
||||
|
||||
rocksdb_options_set_memtable_op_scan_flush_trigger(o, 100);
|
||||
CheckCondition(100 ==
|
||||
rocksdb_options_get_memtable_op_scan_flush_trigger(o));
|
||||
|
||||
rocksdb_options_set_memtable_avg_op_scan_flush_trigger(o, 150);
|
||||
CheckCondition(150 ==
|
||||
rocksdb_options_get_memtable_avg_op_scan_flush_trigger(o));
|
||||
|
||||
rocksdb_options_set_ttl(o, 5000);
|
||||
CheckCondition(5000 == rocksdb_options_get_ttl(o));
|
||||
|
||||
rocksdb_options_set_skip_stats_update_on_db_open(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_skip_stats_update_on_db_open(o));
|
||||
|
||||
rocksdb_options_set_skip_checking_sst_file_sizes_on_db_open(o, 1);
|
||||
CheckCondition(
|
||||
1 == rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(o));
|
||||
|
||||
rocksdb_options_set_max_write_buffer_number(o, 97);
|
||||
CheckCondition(97 == rocksdb_options_get_max_write_buffer_number(o));
|
||||
|
||||
@@ -2146,10 +2309,6 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(23 ==
|
||||
rocksdb_options_get_min_write_buffer_number_to_merge(o));
|
||||
|
||||
rocksdb_options_set_max_write_buffer_number_to_maintain(o, 64);
|
||||
CheckCondition(64 ==
|
||||
rocksdb_options_get_max_write_buffer_number_to_maintain(o));
|
||||
|
||||
rocksdb_options_set_max_write_buffer_size_to_maintain(o, 50000);
|
||||
CheckCondition(50000 ==
|
||||
rocksdb_options_get_max_write_buffer_size_to_maintain(o));
|
||||
@@ -2402,13 +2561,9 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(2.0 ==
|
||||
rocksdb_options_get_max_bytes_for_level_multiplier(copy));
|
||||
CheckCondition(1 == rocksdb_options_get_skip_stats_update_on_db_open(copy));
|
||||
CheckCondition(
|
||||
1 == rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(copy));
|
||||
CheckCondition(97 == rocksdb_options_get_max_write_buffer_number(copy));
|
||||
CheckCondition(23 ==
|
||||
rocksdb_options_get_min_write_buffer_number_to_merge(copy));
|
||||
CheckCondition(
|
||||
64 == rocksdb_options_get_max_write_buffer_number_to_maintain(copy));
|
||||
CheckCondition(50000 ==
|
||||
rocksdb_options_get_max_write_buffer_size_to_maintain(copy));
|
||||
CheckCondition(1 == rocksdb_options_get_enable_pipelined_write(copy));
|
||||
@@ -2572,6 +2727,18 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(100000 ==
|
||||
rocksdb_options_get_periodic_compaction_seconds(o));
|
||||
|
||||
rocksdb_options_set_memtable_op_scan_flush_trigger(copy, 800);
|
||||
CheckCondition(800 ==
|
||||
rocksdb_options_get_memtable_op_scan_flush_trigger(copy));
|
||||
CheckCondition(100 ==
|
||||
rocksdb_options_get_memtable_op_scan_flush_trigger(o));
|
||||
|
||||
rocksdb_options_set_memtable_avg_op_scan_flush_trigger(copy, 900);
|
||||
CheckCondition(
|
||||
900 == rocksdb_options_get_memtable_avg_op_scan_flush_trigger(copy));
|
||||
CheckCondition(150 ==
|
||||
rocksdb_options_get_memtable_avg_op_scan_flush_trigger(o));
|
||||
|
||||
rocksdb_options_set_ttl(copy, 8000);
|
||||
CheckCondition(8000 == rocksdb_options_get_ttl(copy));
|
||||
CheckCondition(5000 == rocksdb_options_get_ttl(o));
|
||||
@@ -2580,12 +2747,6 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(0 == rocksdb_options_get_skip_stats_update_on_db_open(copy));
|
||||
CheckCondition(1 == rocksdb_options_get_skip_stats_update_on_db_open(o));
|
||||
|
||||
rocksdb_options_set_skip_checking_sst_file_sizes_on_db_open(copy, 0);
|
||||
CheckCondition(
|
||||
0 == rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(copy));
|
||||
CheckCondition(
|
||||
1 == rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(o));
|
||||
|
||||
rocksdb_options_set_max_write_buffer_number(copy, 2000);
|
||||
CheckCondition(2000 == rocksdb_options_get_max_write_buffer_number(copy));
|
||||
CheckCondition(97 == rocksdb_options_get_max_write_buffer_number(o));
|
||||
@@ -2596,12 +2757,6 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(23 ==
|
||||
rocksdb_options_get_min_write_buffer_number_to_merge(o));
|
||||
|
||||
rocksdb_options_set_max_write_buffer_number_to_maintain(copy, 128);
|
||||
CheckCondition(
|
||||
128 == rocksdb_options_get_max_write_buffer_number_to_maintain(copy));
|
||||
CheckCondition(64 ==
|
||||
rocksdb_options_get_max_write_buffer_number_to_maintain(o));
|
||||
|
||||
rocksdb_options_set_max_write_buffer_size_to_maintain(copy, 9000);
|
||||
CheckCondition(9000 ==
|
||||
rocksdb_options_get_max_write_buffer_size_to_maintain(copy));
|
||||
@@ -4052,6 +4207,23 @@ int main(int argc, char** argv) {
|
||||
rocksdb_cache_destroy(lru);
|
||||
}
|
||||
|
||||
StartPhase("sst_file_manager");
|
||||
{
|
||||
rocksdb_sst_file_manager_t* sst_file_manager;
|
||||
sst_file_manager = rocksdb_sst_file_manager_create(env);
|
||||
rocksdb_sst_file_manager_set_delete_rate_bytes_per_second(sst_file_manager,
|
||||
1);
|
||||
rocksdb_sst_file_manager_set_max_trash_db_ratio(sst_file_manager, 0.75);
|
||||
|
||||
CheckCondition(1 ==
|
||||
rocksdb_sst_file_manager_get_delete_rate_bytes_per_second(
|
||||
sst_file_manager));
|
||||
CheckCondition(0.75 == rocksdb_sst_file_manager_get_max_trash_db_ratio(
|
||||
sst_file_manager));
|
||||
|
||||
rocksdb_sst_file_manager_destroy(sst_file_manager);
|
||||
}
|
||||
|
||||
StartPhase("cancel_all_background_work");
|
||||
rocksdb_cancel_all_background_work(db, 1);
|
||||
|
||||
|
||||
+59
-20
@@ -110,23 +110,48 @@ void GetInternalTblPropCollFactory(
|
||||
}
|
||||
}
|
||||
|
||||
Status CheckCompressionSupportedWithManager(
|
||||
CompressionType type, UnownedPtr<CompressionManager> mgr) {
|
||||
if (mgr) {
|
||||
if (!mgr->SupportsCompressionType(type)) {
|
||||
return Status::NotSupported("Compression type " +
|
||||
CompressionTypeToString(type) +
|
||||
" is not recognized/supported by this "
|
||||
"version of CompressionManager " +
|
||||
mgr->GetId());
|
||||
}
|
||||
} else {
|
||||
if (!CompressionTypeSupported(type)) {
|
||||
if (type <= kLastBuiltinCompression) {
|
||||
return Status::InvalidArgument("Compression type " +
|
||||
CompressionTypeToString(type) +
|
||||
" is not linked with the binary.");
|
||||
} else {
|
||||
return Status::NotSupported(
|
||||
"Compression type " + CompressionTypeToString(type) +
|
||||
" is not recognized/supported by built-in CompressionManager.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
|
||||
if (!cf_options.compression_per_level.empty()) {
|
||||
for (size_t level = 0; level < cf_options.compression_per_level.size();
|
||||
++level) {
|
||||
if (!CompressionTypeSupported(cf_options.compression_per_level[level])) {
|
||||
return Status::InvalidArgument(
|
||||
"Compression type " +
|
||||
CompressionTypeToString(cf_options.compression_per_level[level]) +
|
||||
" is not linked with the binary.");
|
||||
Status s = CheckCompressionSupportedWithManager(
|
||||
cf_options.compression_per_level[level],
|
||||
cf_options.compression_manager.get());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!CompressionTypeSupported(cf_options.compression)) {
|
||||
return Status::InvalidArgument(
|
||||
"Compression type " +
|
||||
CompressionTypeToString(cf_options.compression) +
|
||||
" is not linked with the binary.");
|
||||
Status s = CheckCompressionSupportedWithManager(
|
||||
cf_options.compression, cf_options.compression_manager.get());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
if (cf_options.compression_opts.zstd_max_train_bytes > 0) {
|
||||
@@ -255,22 +280,18 @@ ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
|
||||
}
|
||||
|
||||
if (result.compaction_style == kCompactionStyleUniversal &&
|
||||
db_options.allow_ingest_behind && result.num_levels < 3) {
|
||||
(db_options.allow_ingest_behind || result.cf_allow_ingest_behind) &&
|
||||
result.num_levels < 3) {
|
||||
result.num_levels = 3;
|
||||
}
|
||||
|
||||
if (result.max_write_buffer_number < 2) {
|
||||
result.max_write_buffer_number = 2;
|
||||
}
|
||||
// fall back max_write_buffer_number_to_maintain if
|
||||
// max_write_buffer_size_to_maintain is not set
|
||||
if (result.max_write_buffer_size_to_maintain < 0) {
|
||||
result.max_write_buffer_size_to_maintain =
|
||||
result.max_write_buffer_number *
|
||||
static_cast<int64_t>(result.write_buffer_size);
|
||||
} else if (result.max_write_buffer_size_to_maintain == 0 &&
|
||||
result.max_write_buffer_number_to_maintain < 0) {
|
||||
result.max_write_buffer_number_to_maintain = result.max_write_buffer_number;
|
||||
}
|
||||
// bloom filter size shouldn't exceed 1/4 of memtable size.
|
||||
if (result.memtable_prefix_bloom_size_ratio > 0.25) {
|
||||
@@ -453,6 +474,21 @@ ColumnFamilyOptions SanitizeCfOptions(const ImmutableDBOptions& db_options,
|
||||
result.preclude_last_level_data_seconds = 0;
|
||||
}
|
||||
|
||||
if (read_only) {
|
||||
if (result.memtable_op_scan_flush_trigger) {
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
"option memtable_op_scan_flush_trigger is sanitized to "
|
||||
"0(disabled) for read only DB.");
|
||||
result.memtable_op_scan_flush_trigger = 0;
|
||||
}
|
||||
if (result.memtable_avg_op_scan_flush_trigger) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options.info_log.get(),
|
||||
"option memtable_avg_op_scan_flush_trigger is sanitized to "
|
||||
"0(disabled) for read only DB.");
|
||||
result.memtable_avg_op_scan_flush_trigger = 0;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -577,7 +613,6 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
mem_(nullptr),
|
||||
imm_(ioptions_.min_write_buffer_number_to_merge,
|
||||
ioptions_.max_write_buffer_number_to_maintain,
|
||||
ioptions_.max_write_buffer_size_to_maintain),
|
||||
super_version_(nullptr),
|
||||
super_version_number_(0),
|
||||
@@ -1208,10 +1243,12 @@ Compaction* ColumnFamilyData::PickCompaction(
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer) {
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
bool require_max_output_level) {
|
||||
auto* result = compaction_picker_->PickCompaction(
|
||||
GetName(), mutable_options, mutable_db_options, existing_snapshots,
|
||||
snapshot_checker, current_->storage_info(), log_buffer);
|
||||
snapshot_checker, current_->storage_info(), log_buffer,
|
||||
require_max_output_level);
|
||||
if (result != nullptr) {
|
||||
result->FinalizeInputInfo(current_);
|
||||
}
|
||||
@@ -1295,7 +1332,7 @@ Compaction* ColumnFamilyData::CompactRange(
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* conflict,
|
||||
uint64_t max_file_num_to_ignore, const std::string& trim_ts) {
|
||||
auto* result = compaction_picker_->CompactRange(
|
||||
auto* result = compaction_picker_->PickCompactionForCompactRange(
|
||||
GetName(), mutable_cf_options, mutable_db_options,
|
||||
current_->storage_info(), input_level, output_level,
|
||||
compact_range_options, begin, end, compaction_end, conflict,
|
||||
@@ -1597,6 +1634,8 @@ Status ColumnFamilyData::SetOptions(
|
||||
Status s = GetColumnFamilyOptionsFromMap(config_opts, cf_opts, options_map,
|
||||
&cf_opts);
|
||||
if (s.ok()) {
|
||||
// FIXME: we should call SanitizeOptions() too or consolidate it with
|
||||
// ValidateOptions().
|
||||
s = ValidateOptions(db_opts, cf_opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
|
||||
+18
-1
@@ -424,7 +424,8 @@ class ColumnFamilyData {
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer);
|
||||
const SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
bool require_max_output_level = false);
|
||||
|
||||
// Check if the passed range overlap with any running compactions.
|
||||
// REQUIRES: DB mutex held
|
||||
@@ -537,6 +538,12 @@ class ColumnFamilyData {
|
||||
assert(!ts_low.empty());
|
||||
const Comparator* ucmp = user_comparator();
|
||||
assert(ucmp);
|
||||
// Guard against resurrected full_history_ts_low persisted in MANIFEST
|
||||
// from previous DB sessions. This could happen if UDT was enabled and then
|
||||
// disabled.
|
||||
if (ucmp->timestamp_size() == 0) {
|
||||
return;
|
||||
}
|
||||
if (full_history_ts_low_.empty() ||
|
||||
ucmp->CompareTimestamp(ts_low, full_history_ts_low_) > 0) {
|
||||
full_history_ts_low_ = std::move(ts_low);
|
||||
@@ -544,6 +551,11 @@ class ColumnFamilyData {
|
||||
}
|
||||
|
||||
const std::string& GetFullHistoryTsLow() const {
|
||||
const Comparator* ucmp = user_comparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() == 0) {
|
||||
assert(full_history_ts_low_.empty());
|
||||
}
|
||||
return full_history_ts_low_;
|
||||
}
|
||||
|
||||
@@ -588,6 +600,11 @@ class ColumnFamilyData {
|
||||
return (mem_->IsEmpty() ? 0 : 1) + imm_.NumNotFlushed();
|
||||
}
|
||||
|
||||
// thread-safe, DB mutex not needed.
|
||||
bool AllowIngestBehind() const {
|
||||
return ioptions_.cf_allow_ingest_behind || ioptions_.allow_ingest_behind;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(
|
||||
|
||||
@@ -72,7 +72,6 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
env_->skip_fsync_ = true;
|
||||
dbname_ = test::PerThreadDBPath("column_family_test");
|
||||
db_options_.create_if_missing = true;
|
||||
db_options_.fail_if_options_file_error = true;
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
@@ -2176,7 +2175,7 @@ TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
ASSERT_TRUE(has_cf2_sst);
|
||||
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_EQ(0, dbfull()->TEST_total_log_size());
|
||||
ASSERT_EQ(0, dbfull()->TEST_wals_total_size());
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
@@ -441,6 +441,10 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, CompressionWithBlockAlign) {
|
||||
if (!Snappy_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("Test requires Snappy support");
|
||||
return;
|
||||
}
|
||||
Options options;
|
||||
options.compression = CompressionType::kNoCompression;
|
||||
options.create_if_missing = true;
|
||||
@@ -530,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) {
|
||||
|
||||
+27
-11
@@ -281,12 +281,13 @@ 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, bool _manual_compaction,
|
||||
const std::string& _trim_ts, double _score, bool _deletion_compaction,
|
||||
bool l0_files_might_overlap, CompactionReason _compaction_reason,
|
||||
const SnapshotChecker* _snapshot_checker,
|
||||
CompactionReason _compaction_reason, const std::string& _trim_ts,
|
||||
double _score, bool l0_files_might_overlap,
|
||||
BlobGarbageCollectionPolicy _blob_garbage_collection_policy,
|
||||
double _blob_garbage_collection_age_cutoff)
|
||||
: input_vstorage_(vstorage),
|
||||
@@ -303,8 +304,10 @@ Compaction::Compaction(
|
||||
output_path_id_(_output_path_id),
|
||||
output_compression_(_compression),
|
||||
output_compression_opts_(_compression_opts),
|
||||
output_temperature_(_output_temperature),
|
||||
deletion_compaction_(_deletion_compaction),
|
||||
output_temperature_override_(_output_temperature_override),
|
||||
deletion_compaction_(_compaction_reason == CompactionReason::kFIFOTtl ||
|
||||
_compaction_reason ==
|
||||
CompactionReason::kFIFOMaxSize),
|
||||
l0_files_might_overlap_(l0_files_might_overlap),
|
||||
inputs_(PopulateWithAtomicBoundaries(vstorage, std::move(_inputs))),
|
||||
grandparents_(std::move(_grandparents)),
|
||||
@@ -321,7 +324,8 @@ Compaction::Compaction(
|
||||
? false
|
||||
: IsBottommostLevel(output_level_, vstorage, inputs_)),
|
||||
is_full_compaction_(IsFullCompaction(vstorage, inputs_)),
|
||||
is_manual_compaction_(_manual_compaction),
|
||||
is_manual_compaction_(_compaction_reason ==
|
||||
CompactionReason::kManualCompaction),
|
||||
trim_ts_(_trim_ts),
|
||||
is_trivial_move_(false),
|
||||
compaction_reason_(_compaction_reason),
|
||||
@@ -349,9 +353,6 @@ Compaction::Compaction(
|
||||
immutable_options_, start_level_,
|
||||
output_level_)) {
|
||||
MarkFilesBeingCompacted(true);
|
||||
if (is_manual_compaction_) {
|
||||
compaction_reason_ = CompactionReason::kManualCompaction;
|
||||
}
|
||||
if (max_subcompactions_ == 0) {
|
||||
max_subcompactions_ = _mutable_db_options.max_subcompactions;
|
||||
}
|
||||
@@ -647,6 +648,8 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
return true;
|
||||
} else if (output_level_ != 0 &&
|
||||
cfd_->ioptions().compaction_style == kCompactionStyleLevel) {
|
||||
// TODO: apply the optimization here to other compaction styles and
|
||||
// compaction/flush to L0.
|
||||
// Maybe use binary search to find right entry instead of linear search?
|
||||
const Comparator* user_cmp = cfd_->user_comparator();
|
||||
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
|
||||
@@ -1126,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,14 +90,14 @@ 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,
|
||||
bool manual_compaction = false, const std::string& trim_ts = "",
|
||||
double score = -1, bool deletion_compaction = false,
|
||||
CompactionReason compaction_reason,
|
||||
const std::string& trim_ts = "", double score = -1,
|
||||
bool l0_files_might_overlap = true,
|
||||
CompactionReason compaction_reason = CompactionReason::kUnknown,
|
||||
BlobGarbageCollectionPolicy blob_garbage_collection_policy =
|
||||
BlobGarbageCollectionPolicy::kUseDefault,
|
||||
double blob_garbage_collection_age_cutoff = -1);
|
||||
@@ -283,6 +283,13 @@ class Compaction {
|
||||
// are non-overlapping and can be trivially moved.
|
||||
bool is_trivial_move() const { return is_trivial_move_; }
|
||||
|
||||
bool is_trivial_copy_compaction() const {
|
||||
return immutable_options_.compaction_style == kCompactionStyleFIFO &&
|
||||
compaction_reason_ == CompactionReason::kChangeTemperature &&
|
||||
mutable_cf_options_.compaction_options_fifo
|
||||
.allow_trivial_copy_when_change_temperature;
|
||||
}
|
||||
|
||||
// How many total levels are there?
|
||||
int number_levels() const { return number_levels_; }
|
||||
|
||||
@@ -403,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_; }
|
||||
|
||||
@@ -456,6 +467,11 @@ class Compaction {
|
||||
const int start_level,
|
||||
const int output_level);
|
||||
|
||||
static bool OutputToNonZeroMaxOutputLevel(int output_level,
|
||||
int max_output_level) {
|
||||
return output_level > 0 && output_level == max_output_level;
|
||||
}
|
||||
|
||||
// If some data cannot be safely migrated "up" the LSM tree due to a change
|
||||
// in the preclude_last_level_data_seconds setting, this indicates a sequence
|
||||
// number for the newest data that must be kept in the last level.
|
||||
@@ -530,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?
|
||||
|
||||
@@ -28,7 +28,7 @@ CompactionIterator::CompactionIterator(
|
||||
SequenceNumber earliest_snapshot,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SequenceNumber job_snapshot, const SnapshotChecker* snapshot_checker,
|
||||
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
|
||||
Env* env, bool report_detailed_time,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
@@ -42,8 +42,8 @@ CompactionIterator::CompactionIterator(
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots, earliest_snapshot,
|
||||
earliest_write_conflict_snapshot, job_snapshot, snapshot_checker, env,
|
||||
report_detailed_time, expect_valid_internal_key, range_del_agg,
|
||||
blob_file_builder, allow_data_in_errors, enforce_single_del_contracts,
|
||||
report_detailed_time, range_del_agg, blob_file_builder,
|
||||
allow_data_in_errors, enforce_single_del_contracts,
|
||||
manual_compaction_canceled,
|
||||
compaction ? std::make_unique<RealCompaction>(compaction) : nullptr,
|
||||
must_count_input_entries, compaction_filter, shutting_down, info_log,
|
||||
@@ -55,7 +55,7 @@ CompactionIterator::CompactionIterator(
|
||||
SequenceNumber earliest_snapshot,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SequenceNumber job_snapshot, const SnapshotChecker* snapshot_checker,
|
||||
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
|
||||
Env* env, bool report_detailed_time,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
@@ -76,16 +76,14 @@ CompactionIterator::CompactionIterator(
|
||||
env_(env),
|
||||
clock_(env_->GetSystemClock().get()),
|
||||
report_detailed_time_(report_detailed_time),
|
||||
expect_valid_internal_key_(expect_valid_internal_key),
|
||||
range_del_agg_(range_del_agg),
|
||||
blob_file_builder_(blob_file_builder),
|
||||
compaction_(std::move(compaction)),
|
||||
compaction_filter_(compaction_filter),
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_canceled_(manual_compaction_canceled),
|
||||
bottommost_level_(!compaction_ ? false
|
||||
: compaction_->bottommost_level() &&
|
||||
!compaction_->allow_ingest_behind()),
|
||||
bottommost_level_(compaction_ && compaction_->bottommost_level() &&
|
||||
!compaction_->allow_ingest_behind()),
|
||||
// snapshots_ cannot be nullptr, but we will assert later in the body of
|
||||
// the constructor.
|
||||
visible_at_tip_(snapshots_ ? snapshots_->empty() : false),
|
||||
@@ -161,6 +159,7 @@ void CompactionIterator::Next() {
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to be valid.
|
||||
if (!s.ok()) {
|
||||
// FIXME: should fail compaction after this fatal logging.
|
||||
ROCKS_LOG_FATAL(
|
||||
info_log_, "Invalid ikey %s in compaction. %s",
|
||||
allow_data_in_errors_ ? key_.ToString(true).c_str() : "hidden",
|
||||
@@ -464,18 +463,9 @@ void CompactionIterator::NextFromInput() {
|
||||
if (!pik_status.ok()) {
|
||||
iter_stats_.num_input_corrupt_records++;
|
||||
|
||||
// If `expect_valid_internal_key_` is false, return the corrupted key
|
||||
// and let the caller decide what to do with it.
|
||||
if (expect_valid_internal_key_) {
|
||||
status_ = pik_status;
|
||||
return;
|
||||
}
|
||||
key_ = current_key_.SetInternalKey(key_);
|
||||
has_current_user_key_ = false;
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
validity_info_.SetValid(ValidContext::kParseKeyError);
|
||||
break;
|
||||
// Always fail compaction when encountering corrupted internal keys
|
||||
status_ = pik_status;
|
||||
return;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
|
||||
if (is_range_del_) {
|
||||
@@ -642,7 +632,8 @@ void CompactionIterator::NextFromInput() {
|
||||
} else if (ikey_.type == kTypeSingleDeletion) {
|
||||
// We can compact out a SingleDelete if:
|
||||
// 1) We encounter the corresponding PUT -OR- we know that this key
|
||||
// doesn't appear past this output level
|
||||
// doesn't appear past this output level and we are not in
|
||||
// ingest_behind mode.
|
||||
// =AND=
|
||||
// 2) We've already returned a record in this snapshot -OR-
|
||||
// there are no earlier earliest_write_conflict_snapshot.
|
||||
@@ -731,6 +722,8 @@ void CompactionIterator::NextFromInput() {
|
||||
"CompactionIterator::NextFromInput:SingleDelete:1",
|
||||
const_cast<Compaction*>(c));
|
||||
if (last_key_seq_zeroed_) {
|
||||
// Drop SD and the next key since they are both in the last
|
||||
// snapshot (since last key has seqno zeroed).
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
assert(bottommost_level_);
|
||||
@@ -841,7 +834,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// iteration. If the next key is corrupt, we return before the
|
||||
// comparison, so the value of has_current_user_key does not matter.
|
||||
has_current_user_key_ = false;
|
||||
if (compaction_ != nullptr &&
|
||||
if (compaction_ != nullptr && !compaction_->allow_ingest_behind() &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_) &&
|
||||
@@ -854,6 +847,9 @@ void CompactionIterator::NextFromInput() {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
} else if (last_key_seq_zeroed_) {
|
||||
// Sequence number zeroing requires bottommost_level_, which is
|
||||
// false with ingest_behind.
|
||||
assert(!compaction_->allow_ingest_behind());
|
||||
// Skip.
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
@@ -870,6 +866,7 @@ void CompactionIterator::NextFromInput() {
|
||||
} else if (last_sequence != kMaxSequenceNumber &&
|
||||
(last_snapshot == current_user_key_snapshot_ ||
|
||||
last_snapshot < current_user_key_snapshot_)) {
|
||||
// rule (A):
|
||||
// If the earliest snapshot is which this key is visible in
|
||||
// is the same as the visibility of a previous instance of the
|
||||
// same key, then this kv is not visible in any snapshot.
|
||||
@@ -878,6 +875,15 @@ void CompactionIterator::NextFromInput() {
|
||||
// Note: Dropping this key will not affect TransactionDB write-conflict
|
||||
// checking since there has already been a record returned for this key
|
||||
// in this snapshot.
|
||||
// When ingest_behind is enabled, it's ok that we drop an overwritten
|
||||
// Delete here. The overwritting key still covers whatever that will be
|
||||
// ingested. Note that we will not drop SingleDelete here as SingleDelte
|
||||
// is handled entirely in its own if clause. This is important, see
|
||||
// example: from new to old: SingleDelete_1, PUT_1, SingleDelete_2, PUT_2,
|
||||
// where all operations are on the same key and PUT_2 is ingested with
|
||||
// ingest_behind=true. If SingleDelete_2 is dropped due to being compacted
|
||||
// together with PUT_1, and then PUT_1 is compacted away together with
|
||||
// SingleDelete_1, PUT_2 can incorrectly becomes visible.
|
||||
if (last_sequence < current_user_key_sequence_) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"key %s, last_sequence (%" PRIu64
|
||||
@@ -887,12 +893,13 @@ void CompactionIterator::NextFromInput() {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
++iter_stats_.num_record_drop_hidden; // rule (A)
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
AdvanceInputIter();
|
||||
} else if (compaction_ != nullptr &&
|
||||
(ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
!compaction_->allow_ingest_behind() &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
@@ -928,11 +935,13 @@ void CompactionIterator::NextFromInput() {
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
bottommost_level_) {
|
||||
assert(compaction_);
|
||||
assert(!compaction_->allow_ingest_behind()); // bottommost_level_ is true
|
||||
// Handle the case where we have a delete key at the bottom most level
|
||||
// We can skip outputting the key iff there are no subsequent puts for
|
||||
// this key
|
||||
assert(!compaction_ || compaction_->KeyNotExistsBeyondOutputLevel(
|
||||
ikey_.user_key, &level_ptrs_));
|
||||
assert(compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_));
|
||||
ParsedInternalKey next_ikey;
|
||||
AdvanceInputIter();
|
||||
#ifndef NDEBUG
|
||||
@@ -974,6 +983,12 @@ void CompactionIterator::NextFromInput() {
|
||||
(compaction_ != nullptr &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)))) {
|
||||
// FIXME: it's possible that we are setting sequence number to 0 as
|
||||
// preferred sequence number here. If cf_ingest_behind is enabled, this
|
||||
// may fail ingestions since they expect all keys above the last level
|
||||
// to have non-zero sequence number. We should probably not allow seqno
|
||||
// zeroing here.
|
||||
//
|
||||
// This section that attempts to swap preferred sequence number will not
|
||||
// be invoked if this is a CompactionIterator created for flush, since
|
||||
// `compaction_` will be nullptr and it's not bottommost either.
|
||||
@@ -1105,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1274,11 +1287,11 @@ void CompactionIterator::PrepareOutput() {
|
||||
//
|
||||
// Can we do the same for levels above bottom level as long as
|
||||
// KeyNotExistsBeyondOutputLevel() return true?
|
||||
if (Valid() && compaction_ != nullptr &&
|
||||
!compaction_->allow_ingest_behind() && bottommost_level_ &&
|
||||
if (Valid() && bottommost_level_ &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
ikey_.type != kTypeMerge && current_key_committed_ &&
|
||||
ikey_.sequence <= preserve_seqno_after_ && !is_range_del_) {
|
||||
assert(compaction_ != nullptr && !compaction_->allow_ingest_behind());
|
||||
if (ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeSingleDeletion && timestamp_size_ == 0)) {
|
||||
ROCKS_LOG_FATAL(
|
||||
|
||||
@@ -145,7 +145,8 @@ class CompactionIterator {
|
||||
}
|
||||
|
||||
bool allow_ingest_behind() const override {
|
||||
return compaction_->immutable_options().allow_ingest_behind;
|
||||
return compaction_->immutable_options().cf_allow_ingest_behind ||
|
||||
compaction_->immutable_options().allow_ingest_behind;
|
||||
}
|
||||
|
||||
bool allow_mmap_reads() const override {
|
||||
@@ -182,17 +183,27 @@ 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,
|
||||
SequenceNumber earliest_snapshot,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SequenceNumber job_snapshot, const SnapshotChecker* snapshot_checker,
|
||||
Env* env, bool report_detailed_time, bool expect_valid_internal_key,
|
||||
Env* env, bool report_detailed_time,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
@@ -212,7 +223,7 @@ class CompactionIterator {
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SequenceNumber job_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
bool report_detailed_time,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
bool allow_data_in_errors,
|
||||
@@ -254,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_; }
|
||||
@@ -347,7 +372,6 @@ class CompactionIterator {
|
||||
Env* env_;
|
||||
SystemClock* clock_;
|
||||
const bool report_detailed_time_;
|
||||
const bool expect_valid_internal_key_;
|
||||
CompactionRangeDelAggregator* range_del_agg_;
|
||||
BlobFileBuilder* blob_file_builder_;
|
||||
std::unique_ptr<CompactionProxy> compaction_;
|
||||
@@ -417,13 +441,15 @@ class CompactionIterator {
|
||||
// NextFromInput()).
|
||||
ParsedInternalKey ikey_;
|
||||
|
||||
// Stores whether ikey_.user_key is valid. If set to false, the user key is
|
||||
// not compared against the current key in the underlying iterator.
|
||||
// Stores whether current_user_key_ is valid. If so, current_user_key_
|
||||
// stores the user key of the last key seen by the iterator.
|
||||
// If false, treat the next key to read as a new user key.
|
||||
bool has_current_user_key_ = false;
|
||||
// If false, the iterator holds a copy of the current compaction iterator
|
||||
// output (or current key in the underlying iterator during NextFromInput()).
|
||||
bool at_next_ = false;
|
||||
|
||||
// A copy of the current internal key.
|
||||
IterKey current_key_;
|
||||
Slice current_user_key_;
|
||||
std::string curr_ts_;
|
||||
@@ -433,8 +459,9 @@ class CompactionIterator {
|
||||
// True if the iterator has already returned a record for the current key.
|
||||
bool has_outputted_key_ = false;
|
||||
|
||||
// truncated the value of the next key and output it without applying any
|
||||
// compaction rules. This is used for outputting a put after a single delete.
|
||||
// Truncate the value of the next key and output it without applying any
|
||||
// compaction rules. This is an optimization for outputting a put after
|
||||
// a single delete. See more in `NextFromInput()` under Optimization 3.
|
||||
bool clear_and_output_next_key_ = false;
|
||||
|
||||
MergeOutputIterator merge_out_iter_;
|
||||
|
||||
@@ -294,7 +294,7 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
snapshots_.empty() ? kMaxSequenceNumber : snapshots_.at(0),
|
||||
earliest_write_conflict_snapshot, kMaxSequenceNumber,
|
||||
snapshot_checker_.get(), Env::Default(),
|
||||
false /* report_detailed_time */, false, range_del_agg_.get(),
|
||||
false /* report_detailed_time */, range_del_agg_.get(),
|
||||
nullptr /* blob_file_builder */, true /*allow_data_in_errors*/,
|
||||
true /*enforce_single_del_contracts*/,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse_,
|
||||
@@ -374,8 +374,7 @@ TEST_P(CompactionIteratorTest, EmptyResult) {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
// If there is a corruption after a single deletion, the corrupted key should
|
||||
// be preserved.
|
||||
// If there is a corruption after a single deletion, the compaction should fail.
|
||||
TEST_P(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
|
||||
InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 3, kTypeValue, true),
|
||||
@@ -386,14 +385,10 @@ TEST_P(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
|
||||
ASSERT_EQ(test::KeyStr("a", 5, kTypeSingleDeletion),
|
||||
c_iter_->key().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
ASSERT_EQ(test::KeyStr("a", 3, kTypeValue, true), c_iter_->key().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
ASSERT_EQ(test::KeyStr("b", 10, kTypeValue), c_iter_->key().ToString());
|
||||
c_iter_->Next();
|
||||
ASSERT_OK(c_iter_->status());
|
||||
// The iterator should now fail when encountering the corrupted key
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
ASSERT_FALSE(c_iter_->status().ok());
|
||||
ASSERT_TRUE(c_iter_->status().IsCorruption());
|
||||
}
|
||||
|
||||
// Tests compatibility of TimedPut and SingleDelete. TimedPut should act as if
|
||||
|
||||
+1116
-417
File diff suppressed because it is too large.
Load diff
+233
-57
@@ -142,27 +142,27 @@ class SubcompactionState;
|
||||
|
||||
class CompactionJob {
|
||||
public:
|
||||
CompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory,
|
||||
FSDirectory* blob_output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, JobContext* job_context,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "", std::string trim_ts = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
int* bg_compaction_scheduled = nullptr,
|
||||
int* bg_bottom_compaction_scheduled = nullptr);
|
||||
CompactionJob(int job_id, Compaction* compaction,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory,
|
||||
FSDirectory* blob_output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
JobContext* job_context, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, bool paranoid_file_checks,
|
||||
bool measure_io_stats, const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id = "",
|
||||
const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "", std::string trim_ts = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
int* bg_compaction_scheduled = nullptr,
|
||||
int* bg_bottom_compaction_scheduled = nullptr);
|
||||
|
||||
virtual ~CompactionJob();
|
||||
|
||||
@@ -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();
|
||||
@@ -225,7 +237,7 @@ class CompactionJob {
|
||||
private:
|
||||
friend class CompactionJobTestBase;
|
||||
|
||||
// Collect the following stats from Input Table Properties
|
||||
// Collect the following stats from input files and table properties
|
||||
// - num_input_files_in_non_output_levels
|
||||
// - num_input_files_in_output_level
|
||||
// - bytes_read_non_output_levels
|
||||
@@ -242,14 +254,15 @@ class CompactionJob {
|
||||
// num_input_range_del are calculated successfully.
|
||||
//
|
||||
// This should be called only once for compactions (not per subcompaction)
|
||||
bool BuildStatsFromInputTableProperties(
|
||||
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;
|
||||
|
||||
Status VerifyInputRecordCount(uint64_t num_input_range_del) const;
|
||||
Status VerifyOutputRecordCount() const;
|
||||
|
||||
// Generates a histogram representing potential divisions of key ranges from
|
||||
// the input. It adds the starting and/or ending keys of certain input files
|
||||
@@ -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();
|
||||
@@ -278,18 +295,145 @@ class CompactionJob {
|
||||
// Release all reserved threads and update the compaction limits.
|
||||
void ReleaseSubcompactionResources();
|
||||
|
||||
void InitializeCompactionRun();
|
||||
void RunSubcompactions();
|
||||
void UpdateTimingStats(uint64_t start_micros);
|
||||
void RemoveEmptyOutputs();
|
||||
bool HasNewBlobFiles() const;
|
||||
Status CollectSubcompactionErrors();
|
||||
Status SyncOutputDirectories();
|
||||
Status VerifyOutputFiles();
|
||||
void SetOutputTableProperties();
|
||||
// Aggregates subcompaction output stats to internal stat, and aggregates
|
||||
// subcompaction's compaction job stats to the whole entire surrounding
|
||||
// compaction job stats.
|
||||
void AggregateSubcompactionOutputAndJobStats();
|
||||
Status VerifyCompactionRecordCounts(bool stats_built_from_input_table_prop,
|
||||
uint64_t num_input_range_del);
|
||||
void FinalizeCompactionRun(const Status& status,
|
||||
bool stats_built_from_input_table_prop,
|
||||
uint64_t num_input_range_del);
|
||||
|
||||
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);
|
||||
@@ -321,21 +465,8 @@ class CompactionJob {
|
||||
FSDirectory* blob_output_directory_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
ErrorHandler* db_error_handler_;
|
||||
// If there were two snapshots with seq numbers s1 and
|
||||
// s2 and s1 < s2, and if we find two instances of a key k1 then lies
|
||||
// entirely within s1 and s2, then the earlier version of k1 can be safely
|
||||
// deleted because that version is not visible in any snapshot.
|
||||
std::vector<SequenceNumber> existing_snapshots_;
|
||||
|
||||
SequenceNumber earliest_snapshot_;
|
||||
|
||||
// This is the earliest snapshot that could be used for write-conflict
|
||||
// checking by a transaction. For any user-key newer than this snapshot, we
|
||||
// should make sure not to remove evidence that a write occurred.
|
||||
SequenceNumber earliest_write_conflict_snapshot_;
|
||||
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
|
||||
JobContext* job_context_;
|
||||
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
@@ -379,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);
|
||||
@@ -386,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
|
||||
@@ -427,8 +598,9 @@ struct CompactionServiceInput {
|
||||
// CompactionServiceOutputFile is the metadata for the output SST file
|
||||
struct CompactionServiceOutputFile {
|
||||
std::string file_name;
|
||||
SequenceNumber smallest_seqno;
|
||||
SequenceNumber largest_seqno;
|
||||
uint64_t file_size{};
|
||||
SequenceNumber smallest_seqno{};
|
||||
SequenceNumber largest_seqno{};
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time = kUnknownOldestAncesterTime;
|
||||
@@ -436,24 +608,26 @@ struct CompactionServiceOutputFile {
|
||||
uint64_t epoch_number = kUnknownEpochNumber;
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
uint64_t paranoid_hash;
|
||||
uint64_t paranoid_hash{};
|
||||
bool marked_for_compaction;
|
||||
UniqueId64x2 unique_id{};
|
||||
TableProperties table_properties;
|
||||
bool is_proximal_level_output;
|
||||
Temperature file_temperature;
|
||||
Temperature file_temperature = Temperature::kUnknown;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _epoch_number, const std::string& _file_checksum,
|
||||
const std::string& name, uint64_t size, SequenceNumber smallest,
|
||||
SequenceNumber largest, std::string _smallest_internal_key,
|
||||
std::string _largest_internal_key, uint64_t _oldest_ancester_time,
|
||||
uint64_t _file_creation_time, uint64_t _epoch_number,
|
||||
const std::string& _file_checksum,
|
||||
const std::string& _file_checksum_func_name, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id,
|
||||
const TableProperties& _table_properties, bool _is_proximal_level_output,
|
||||
Temperature _file_temperature)
|
||||
: file_name(name),
|
||||
file_size(size),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
smallest_internal_key(std::move(_smallest_internal_key)),
|
||||
@@ -521,9 +695,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
JobContext* job_context, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, const std::string& dbname,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string output_path,
|
||||
@@ -532,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"
|
||||
@@ -43,7 +44,6 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
ASSERT_EQ(compaction_job_stats.elapsed_micros, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_input_records, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_input_files, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.num_input_files_at_output_level, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.num_output_records, 0U);
|
||||
@@ -52,7 +52,6 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
ASSERT_TRUE(compaction_job_stats.is_manual_compaction);
|
||||
ASSERT_FALSE(compaction_job_stats.is_remote_compaction);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_bytes, 0U);
|
||||
ASSERT_EQ(compaction_job_stats.total_output_bytes, 0U);
|
||||
|
||||
ASSERT_EQ(compaction_job_stats.total_input_raw_key_bytes, 0U);
|
||||
@@ -212,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_),
|
||||
@@ -232,11 +231,6 @@ class CompactionJobTestBase : public testing::Test {
|
||||
// set default for the tests
|
||||
mutable_cf_options_.target_file_size_base = 1024 * 1024;
|
||||
mutable_cf_options_.max_compaction_bytes = 10 * 1024 * 1024;
|
||||
|
||||
// Turn off compaction_verify_record_count MockTables
|
||||
if (table_type == TableTypeForTest::kMockTable) {
|
||||
db_options_.compaction_verify_record_count = false;
|
||||
}
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
@@ -465,9 +459,10 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ReadOptions read_opts;
|
||||
Status s = cf_options_.table_factory->NewTableReader(
|
||||
read_opts,
|
||||
TableReaderOptions(cfd->ioptions(), nullptr, FileOptions(),
|
||||
TableReaderOptions(cfd->ioptions(), /*prefix_extractor=*/nullptr,
|
||||
/*compression_manager=*/nullptr, FileOptions(),
|
||||
cfd_->internal_comparator(),
|
||||
0 /* block_protection_bytes_per_key */),
|
||||
/*block_protection_bytes_per_key=*/0),
|
||||
std::move(freader), file_size, &table_reader, false);
|
||||
ASSERT_OK(s);
|
||||
assert(table_reader);
|
||||
@@ -551,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;
|
||||
@@ -600,11 +595,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
const std::vector<std::vector<FileMetaData*>>& input_files,
|
||||
const std::vector<int> input_levels,
|
||||
std::function<void(Compaction& comp)>&& verify_func,
|
||||
const std::vector<SequenceNumber>& snapshots = {}) {
|
||||
std::vector<SequenceNumber>&& snapshots = {}) {
|
||||
const int kLastLevel = cf_options_.num_levels - 1;
|
||||
verify_per_key_placement_ = std::move(verify_func);
|
||||
mock::KVVector empty_map;
|
||||
RunCompaction(input_files, input_levels, {empty_map}, snapshots,
|
||||
RunCompaction(input_files, input_levels, {empty_map}, std::move(snapshots),
|
||||
kMaxSequenceNumber, kLastLevel, false);
|
||||
}
|
||||
|
||||
@@ -613,7 +608,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
const std::vector<std::vector<FileMetaData*>>& input_files,
|
||||
const std::vector<int>& input_levels,
|
||||
const std::vector<mock::KVVector>& expected_results,
|
||||
const std::vector<SequenceNumber>& snapshots = {},
|
||||
std::vector<SequenceNumber>&& snapshots = {},
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
int output_level = 1, bool verify = true,
|
||||
std::vector<uint64_t> expected_oldest_blob_file_numbers = {},
|
||||
@@ -657,7 +652,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
mutable_cf_options_.max_compaction_bytes, 0, kNoCompression,
|
||||
cfd->GetLatestMutableCFOptions().compression_opts,
|
||||
Temperature::kUnknown, max_subcompactions, grandparents,
|
||||
/*earliest_snapshot*/ std::nullopt, /*snapshot_checker*/ nullptr, true);
|
||||
/*earliest_snapshot*/ std::nullopt, /*snapshot_checker*/ nullptr,
|
||||
CompactionReason::kManualCompaction);
|
||||
compaction.FinalizeInputInfo(cfd->current());
|
||||
|
||||
assert(db_options_.info_log);
|
||||
@@ -670,13 +666,15 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ucmp_->timestamp_size() == full_history_ts_low_.size());
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
JobContext job_context(1, false /* create_superversion */);
|
||||
job_context.InitSnapshotContext(snapshot_checker, nullptr,
|
||||
earliest_write_conflict_snapshot,
|
||||
std::move(snapshots));
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, mutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, &job_context,
|
||||
table_cache_, &event_logger, false, false, dbname_,
|
||||
&compaction_job_stats_, Env::Priority::USER, nullptr /* IOTracer */,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, &job_context, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_,
|
||||
Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
env_->GenerateUniqueId(), DBImpl::GenerateDbSessionId(nullptr),
|
||||
full_history_ts_low_);
|
||||
@@ -1674,6 +1672,7 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
UniqueId64x2 id{rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX)};
|
||||
result.output_files.emplace_back(
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)) /* file_name */,
|
||||
rnd64.Uniform(UINT64_MAX) /* file_size */,
|
||||
rnd64.Uniform(UINT64_MAX) /* smallest_seqno */,
|
||||
rnd64.Uniform(UINT64_MAX) /* largest_seqno */,
|
||||
rnd.RandomBinaryString(
|
||||
@@ -2040,7 +2039,7 @@ TEST_F(CompactionJobTest, CutToAlignGrandparentBoundarySameKey) {
|
||||
snapshots.emplace_back(i);
|
||||
}
|
||||
RunCompaction({lvl0_files, lvl1_files}, input_levels,
|
||||
{expected_file1, expected_file2}, snapshots);
|
||||
{expected_file1, expected_file2}, std::move(snapshots));
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, CutForMaxCompactionBytesSameKey) {
|
||||
@@ -2099,7 +2098,8 @@ TEST_F(CompactionJobTest, CutForMaxCompactionBytesSameKey) {
|
||||
snapshots.emplace_back(i);
|
||||
}
|
||||
RunCompaction({lvl0_files, lvl1_files}, input_levels,
|
||||
{expected_file1, expected_file2, expected_file3}, snapshots);
|
||||
{expected_file1, expected_file2, expected_file3},
|
||||
std::move(snapshots));
|
||||
}
|
||||
|
||||
class CompactionJobTimestampTest : public CompactionJobTestBase {
|
||||
@@ -2410,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) {
|
||||
|
||||
@@ -54,7 +54,9 @@ Status CompactionOutputs::Finish(
|
||||
}
|
||||
current_output().finished = true;
|
||||
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;
|
||||
}
|
||||
@@ -357,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()) {
|
||||
@@ -368,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_;
|
||||
|
||||
|
||||
@@ -333,11 +333,13 @@ bool CompactionPicker::AreFilesInCompaction(
|
||||
return false;
|
||||
}
|
||||
|
||||
Compaction* CompactionPicker::CompactFiles(
|
||||
Compaction* CompactionPicker::PickCompactionForCompactFiles(
|
||||
const CompactionOptions& compact_options,
|
||||
const std::vector<CompactionInputFiles>& input_files, int output_level,
|
||||
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, uint32_t output_path_id) {
|
||||
const MutableDBOptions& mutable_db_options, uint32_t output_path_id,
|
||||
std::optional<SequenceNumber> earliest_snapshot,
|
||||
const SnapshotChecker* snapshot_checker) {
|
||||
#ifndef NDEBUG
|
||||
assert(input_files.size());
|
||||
// This compaction output should not overlap with a running compaction as
|
||||
@@ -373,15 +375,16 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
// 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 */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr, true);
|
||||
/* grandparents */ {}, earliest_snapshot, snapshot_checker,
|
||||
CompactionReason::kManualCompaction);
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
}
|
||||
@@ -601,7 +604,7 @@ void CompactionPicker::GetGrandparents(
|
||||
}
|
||||
}
|
||||
|
||||
Compaction* CompactionPicker::CompactRange(
|
||||
Compaction* CompactionPicker::PickCompactionForCompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
int input_level, int output_level,
|
||||
@@ -617,8 +620,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// Universal compaction with more than one level always compacts all the
|
||||
// files together to the last level.
|
||||
assert(vstorage->num_levels() > 1);
|
||||
int max_output_level =
|
||||
vstorage->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
int max_output_level = vstorage->MaxOutputLevel(
|
||||
ioptions_.cf_allow_ingest_behind || ioptions_.allow_ingest_behind);
|
||||
// DBImpl::CompactRange() set output level to be the last level
|
||||
assert(output_level == max_output_level);
|
||||
// DBImpl::RunManualCompaction will make full range for universal compaction
|
||||
@@ -677,13 +680,11 @@ Compaction* CompactionPicker::CompactRange(
|
||||
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,
|
||||
/* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* deletion_compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUnknown,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kManualCompaction,
|
||||
trim_ts, /* score */ -1,
|
||||
/* l0_files_might_overlap */ true,
|
||||
compact_range_options.blob_garbage_collection_policy,
|
||||
compact_range_options.blob_garbage_collection_age_cutoff);
|
||||
|
||||
@@ -870,12 +871,11 @@ Compaction* CompactionPicker::CompactRange(
|
||||
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,
|
||||
/* is manual */ true, trim_ts, /* score */ -1,
|
||||
/* deletion_compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUnknown,
|
||||
CompactionReason::kManualCompaction, trim_ts, /* score */ -1,
|
||||
/* l0_files_might_overlap */ true,
|
||||
compact_range_options.blob_garbage_collection_policy,
|
||||
compact_range_options.blob_garbage_collection_age_cutoff);
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) = 0;
|
||||
LogBuffer* log_buffer, bool require_max_output_level) = 0;
|
||||
|
||||
// The returned Compaction might not include the whole requested range.
|
||||
// In that case, compaction_end will be set to the next key that needs
|
||||
@@ -75,7 +75,7 @@ class CompactionPicker {
|
||||
// *compaction_end should point to valid InternalKey!
|
||||
// REQUIRES: If not compacting all levels (input_level == kCompactAllLevels),
|
||||
// then levels between input_level and output_level should be empty.
|
||||
virtual Compaction* CompactRange(
|
||||
virtual Compaction* PickCompactionForCompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
int input_level, int output_level,
|
||||
@@ -117,12 +117,17 @@ class CompactionPicker {
|
||||
// Caller must provide a set of input files that has been passed through
|
||||
// `SanitizeAndConvertCompactionInputFiles` earlier. The lock should not be
|
||||
// released between that call and this one.
|
||||
Compaction* CompactFiles(const CompactionOptions& compact_options,
|
||||
const std::vector<CompactionInputFiles>& input_files,
|
||||
int output_level, VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
uint32_t output_path_id);
|
||||
//
|
||||
// TODO - Remove default values for earliest_snapshot and snapshot_checker
|
||||
// and require all callers to pass them in so that DB::CompactFiles() can
|
||||
// also benefit from Standalone Range Tombstone Optimization
|
||||
Compaction* PickCompactionForCompactFiles(
|
||||
const CompactionOptions& compact_options,
|
||||
const std::vector<CompactionInputFiles>& input_files, int output_level,
|
||||
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, uint32_t output_path_id,
|
||||
std::optional<SequenceNumber> earliest_snapshot = std::nullopt,
|
||||
const SnapshotChecker* snapshot_checker = nullptr);
|
||||
|
||||
// Converts a set of compaction input file numbers into
|
||||
// a list of CompactionInputFiles.
|
||||
@@ -272,23 +277,23 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots*/,
|
||||
const SnapshotChecker* /*snapshot_checker*/,
|
||||
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */) override {
|
||||
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */,
|
||||
bool /*require_max_output_level*/ = false) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Always return "nullptr"
|
||||
Compaction* CompactRange(const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
VersionStorageInfo* /*vstorage*/,
|
||||
int /*input_level*/, int /*output_level*/,
|
||||
const CompactRangeOptions& /*compact_range_options*/,
|
||||
const InternalKey* /*begin*/,
|
||||
const InternalKey* /*end*/,
|
||||
InternalKey** /*compaction_end*/,
|
||||
bool* /*manual_conflict*/,
|
||||
uint64_t /*max_file_num_to_ignore*/,
|
||||
const std::string& /*trim_ts*/) override {
|
||||
Compaction* PickCompactionForCompactRange(
|
||||
const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
VersionStorageInfo* /*vstorage*/, int /*input_level*/,
|
||||
int /*output_level*/,
|
||||
const CompactRangeOptions& /*compact_range_options*/,
|
||||
const InternalKey* /*begin*/, const InternalKey* /*end*/,
|
||||
InternalKey** /*compaction_end*/, bool* /*manual_conflict*/,
|
||||
uint64_t /*max_file_num_to_ignore*/,
|
||||
const std::string& /*trim_ts*/) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,14 +124,11 @@ 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,
|
||||
/* is manual */ false,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kFIFOTtl,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kFIFOTtl);
|
||||
/* l0_files_might_overlap */ true);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -196,15 +193,13 @@ 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, /* is manual */ false,
|
||||
/* snapshot_checker */ nullptr,
|
||||
CompactionReason::kFIFOReduceNumFiles,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false,
|
||||
/* l0_files_might_overlap */ true,
|
||||
CompactionReason::kFIFOReduceNumFiles);
|
||||
/* l0_files_might_overlap */ true);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -261,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];
|
||||
@@ -294,14 +292,11 @@ 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,
|
||||
/* is manual */ false,
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kFIFOMaxSize,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true,
|
||||
/* l0_files_might_overlap */ true, CompactionReason::kFIFOMaxSize);
|
||||
/* l0_files_might_overlap */ true);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -392,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;
|
||||
}
|
||||
}
|
||||
@@ -416,10 +413,9 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
mutable_cf_options.compression, mutable_cf_options.compression_opts,
|
||||
compaction_target_temp,
|
||||
/* max_subcompactions */ 0, {}, /* earliest_snapshot */ std::nullopt,
|
||||
/* snapshot_checker */ nullptr,
|
||||
/* is manual */ false, /* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false, /* l0_files_might_overlap */ true,
|
||||
CompactionReason::kChangeTemperature);
|
||||
/* snapshot_checker */ nullptr, CompactionReason::kChangeTemperature,
|
||||
/* trim_ts */ "", vstorage->CompactionScore(0),
|
||||
/* l0_files_might_overlap */ true);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -428,7 +424,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
|
||||
Compaction* c = nullptr;
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
c = PickTTLCompaction(cf_name, mutable_cf_options, mutable_db_options,
|
||||
@@ -446,7 +442,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* FIFOCompactionPicker::CompactRange(
|
||||
Compaction* FIFOCompactionPicker::PickCompactionForCompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
int input_level, int output_level,
|
||||
|
||||
@@ -23,18 +23,17 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* version, LogBuffer* log_buffer) override;
|
||||
VersionStorageInfo* version, LogBuffer* log_buffer,
|
||||
bool /* require_max_output_level*/ = false) override;
|
||||
|
||||
Compaction* CompactRange(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* vstorage, int input_level,
|
||||
int output_level,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore,
|
||||
const std::string& trim_ts) override;
|
||||
Compaction* PickCompactionForCompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
int input_level, int output_level,
|
||||
const CompactRangeOptions& compact_range_options,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict,
|
||||
uint64_t max_file_num_to_ignore, const std::string& trim_ts) override;
|
||||
|
||||
// The maximum allowed output level. Always returns 0.
|
||||
int MaxOutputLevel() const override { return 0; }
|
||||
|
||||
@@ -145,7 +145,6 @@ class LevelCompactionBuilder {
|
||||
int parent_index_ = -1;
|
||||
int base_index_ = -1;
|
||||
double start_level_score_ = 0;
|
||||
bool is_manual_ = false;
|
||||
bool is_l0_trivial_move_ = false;
|
||||
CompactionInputFiles start_level_inputs_;
|
||||
std::vector<CompactionInputFiles> compaction_inputs_;
|
||||
@@ -558,12 +557,11 @@ 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,
|
||||
is_manual_,
|
||||
/* trim_ts */ "", start_level_score_, false /* deletion_compaction */,
|
||||
l0_files_might_overlap, compaction_reason_);
|
||||
compaction_reason_,
|
||||
/* trim_ts */ "", start_level_score_, l0_files_might_overlap);
|
||||
|
||||
// If it's level 0 compaction, make sure we don't execute any other level 0
|
||||
// compactions in parallel
|
||||
@@ -978,7 +976,7 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /*existing_snapshots */,
|
||||
const SnapshotChecker* /*snapshot_checker*/, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer, bool /* require_max_output_level*/) {
|
||||
LevelCompactionBuilder builder(cf_name, vstorage, this, log_buffer,
|
||||
mutable_cf_options, ioptions_,
|
||||
mutable_db_options);
|
||||
|
||||
@@ -25,7 +25,8 @@ class LevelCompactionPicker : public CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& /* existing_snapshots */,
|
||||
const SnapshotChecker* /* snapshot_checker */,
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer) override;
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer,
|
||||
bool /*require_max_output_level*/ = false) override;
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
};
|
||||
|
||||
@@ -544,41 +544,48 @@ TEST_F(CompactionPickerTest, NeedsCompactionUniversal) {
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionUniversalIngestBehindReservedLevel) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
NewVersionStorage(3 /* num_levels */, kCompactionStyleUniversal);
|
||||
ioptions_.allow_ingest_behind = true;
|
||||
ioptions_.num_levels = 3;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
UpdateVersionStorageInfo();
|
||||
// must return false when there's no files.
|
||||
ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()),
|
||||
false);
|
||||
for (bool cf_option : {false, true}) {
|
||||
SCOPED_TRACE("cf_option = " + std::to_string(cf_option));
|
||||
const uint64_t kFileSize = 100000;
|
||||
NewVersionStorage(3 /* num_levels */, kCompactionStyleUniversal);
|
||||
if (cf_option) {
|
||||
ioptions_.cf_allow_ingest_behind = true;
|
||||
} else {
|
||||
ioptions_.allow_ingest_behind = true;
|
||||
}
|
||||
ioptions_.num_levels = 3;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
UpdateVersionStorageInfo();
|
||||
// must return false when there's no files.
|
||||
ASSERT_EQ(universal_compaction_picker.NeedsCompaction(vstorage_.get()),
|
||||
false);
|
||||
|
||||
NewVersionStorage(3, kCompactionStyleUniversal);
|
||||
NewVersionStorage(3, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
|
||||
Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
|
||||
Add(1, 5U, "100", "151", kFileSize, 0, 200, 251);
|
||||
Add(1, 3U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(2, 6U, "120", "200", kFileSize, 0, 20, 100);
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(0, 2U, "201", "250", kFileSize, 0, 401, 450);
|
||||
Add(0, 4U, "260", "300", kFileSize, 0, 260, 300);
|
||||
Add(1, 5U, "100", "151", kFileSize, 0, 200, 251);
|
||||
Add(1, 3U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(2, 6U, "120", "200", kFileSize, 0, 20, 100);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_,
|
||||
/*existing_snapshots=*/{}, /* snapshot_checker */ nullptr,
|
||||
vstorage_.get(), &log_buffer_));
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_,
|
||||
/*existing_snapshots=*/{}, /* snapshot_checker */ nullptr,
|
||||
vstorage_.get(), &log_buffer_));
|
||||
|
||||
// output level should be the one above the bottom-most
|
||||
ASSERT_EQ(1, compaction->output_level());
|
||||
// output level should be the one above the bottom-most
|
||||
ASSERT_EQ(1, compaction->output_level());
|
||||
|
||||
// input should not include the reserved level
|
||||
const std::vector<CompactionInputFiles>* inputs = compaction->inputs();
|
||||
for (const auto& compaction_input : *inputs) {
|
||||
if (!compaction_input.empty()) {
|
||||
ASSERT_LT(compaction_input.level, 2);
|
||||
// input should not include the reserved level
|
||||
const std::vector<CompactionInputFiles>* inputs = compaction->inputs();
|
||||
for (const auto& compaction_input : *inputs) {
|
||||
if (!compaction_input.empty()) {
|
||||
ASSERT_LT(compaction_input.level, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1134,10 +1141,15 @@ TEST_F(CompactionPickerTest, FIFOToCold1) {
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.file_temperature_age_thresholds = {
|
||||
{Temperature::kCold, kColdThreshold}};
|
||||
fifo_options_.allow_trivial_copy_when_change_temperature = true;
|
||||
fifo_options_.trivial_copy_buffer_size = 16 * 1024 * 1024;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
auto copiedIOptions = ioptions_;
|
||||
copiedIOptions.compaction_style = kCompactionStyleFIFO;
|
||||
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
@@ -1166,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());
|
||||
}
|
||||
@@ -1186,7 +1198,10 @@ TEST_F(CompactionPickerTest, FIFOToColdMaxCompactionSize) {
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 9;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
auto copiedIOptions = ioptions_;
|
||||
copiedIOptions.compaction_style = kCompactionStyleFIFO;
|
||||
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
@@ -1233,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());
|
||||
}
|
||||
@@ -1253,7 +1268,10 @@ TEST_F(CompactionPickerTest, FIFOToColdWithExistingCold) {
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
auto copiedIOptions = ioptions_;
|
||||
copiedIOptions.compaction_style = kCompactionStyleFIFO;
|
||||
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
@@ -1298,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());
|
||||
}
|
||||
@@ -1318,7 +1336,10 @@ TEST_F(CompactionPickerTest, FIFOToColdWithHotBetweenCold) {
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
auto copiedIOptions = ioptions_;
|
||||
copiedIOptions.compaction_style = kCompactionStyleFIFO;
|
||||
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
@@ -1362,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());
|
||||
}
|
||||
@@ -1385,7 +1406,10 @@ TEST_F(CompactionPickerTest, FIFOToHotAndWarm) {
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 100;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
auto copiedIOptions = ioptions_;
|
||||
copiedIOptions.compaction_style = kCompactionStyleFIFO;
|
||||
FIFOCompactionPicker fifo_compaction_picker(copiedIOptions, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
@@ -1440,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;
|
||||
@@ -2672,13 +2719,14 @@ TEST_F(CompactionPickerTest, CompactRangeMaxCompactionBytes) {
|
||||
bool manual_conflict = false;
|
||||
InternalKey manual_end;
|
||||
InternalKey* manual_end_ptr = &manual_end;
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.CompactRange(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
/*input_level=*/1, /*output_level=*/2,
|
||||
/*compact_range_options*/ {}, /*begin=*/nullptr, /*end=*/nullptr,
|
||||
&manual_end_ptr, &manual_conflict,
|
||||
/*max_file_num_to_ignore=*/std::numeric_limits<uint64_t>::max(),
|
||||
/*trim_ts=*/""));
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
level_compaction_picker.PickCompactionForCompactRange(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
/*input_level=*/1, /*output_level=*/2,
|
||||
/*compact_range_options*/ {}, /*begin=*/nullptr, /*end=*/nullptr,
|
||||
&manual_end_ptr, &manual_conflict,
|
||||
/*max_file_num_to_ignore=*/std::numeric_limits<uint64_t>::max(),
|
||||
/*trim_ts=*/""));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_levels());
|
||||
ASSERT_EQ(2, compaction->output_level());
|
||||
@@ -3610,7 +3658,7 @@ TEST_F(CompactionPickerTest, UniversalMarkedManualCompaction) {
|
||||
bool manual_conflict = false;
|
||||
InternalKey* manual_end = nullptr;
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.CompactRange(
|
||||
universal_compaction_picker.PickCompactionForCompactRange(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
ColumnFamilyData::kCompactAllLevels, 6, CompactRangeOptions(),
|
||||
nullptr, nullptr, &manual_end, &manual_conflict,
|
||||
@@ -3814,9 +3862,10 @@ TEST_F(CompactionPickerU64TsTest, Overlap) {
|
||||
std::vector<CompactionInputFiles> input_files;
|
||||
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input, vstorage_.get(), CompactionOptions()));
|
||||
std::unique_ptr<Compaction> comp1(level_compaction_picker.CompactFiles(
|
||||
CompactionOptions(), input_files, level, vstorage_.get(),
|
||||
mutable_cf_options_, mutable_db_options_, /*output_path_id=*/0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
level_compaction_picker.PickCompactionForCompactFiles(
|
||||
CompactionOptions(), input_files, level, vstorage_.get(),
|
||||
mutable_cf_options_, mutable_db_options_, /*output_path_id=*/0));
|
||||
|
||||
{
|
||||
// [600, ts=50000] to [600, ts=50000] is the range to check.
|
||||
@@ -3925,9 +3974,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, OverlapWithNormalCompaction) {
|
||||
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(level_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
level_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
input_set.clear();
|
||||
input_files.clear();
|
||||
@@ -3971,9 +4021,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, NormalCompactionOverlap) {
|
||||
ASSERT_OK(level_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(level_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
level_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
input_set.clear();
|
||||
input_files.clear();
|
||||
@@ -4013,9 +4064,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
input_set.clear();
|
||||
input_files.clear();
|
||||
@@ -4060,9 +4112,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, NormalCompactionOverlapUniversal) {
|
||||
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
input_set.clear();
|
||||
input_files.clear();
|
||||
@@ -4108,9 +4161,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, ProximalOverlapUniversal) {
|
||||
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
input_set.clear();
|
||||
input_files.clear();
|
||||
@@ -4159,9 +4213,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest, LastLevelOnlyOverlapUniversal) {
|
||||
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
// cannot compact file 41 if the preclude_last_level feature is on, otherwise
|
||||
// compact file 41 is okay.
|
||||
@@ -4217,9 +4272,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
ASSERT_TRUE(comp1);
|
||||
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
|
||||
@@ -4235,9 +4291,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
ASSERT_FALSE(universal_compaction_picker.FilesRangeOverlapWithCompaction(
|
||||
input_files, 5, Compaction::kInvalidLevel));
|
||||
|
||||
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp2(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
ASSERT_TRUE(comp2);
|
||||
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
|
||||
}
|
||||
@@ -4273,9 +4330,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
ASSERT_TRUE(comp1);
|
||||
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
|
||||
@@ -4293,9 +4351,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
vstorage_.get(), mutable_cf_options_, ioptions_, 6, 6)));
|
||||
|
||||
if (!enable_per_key_placement_) {
|
||||
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp2(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
ASSERT_TRUE(comp2);
|
||||
ASSERT_EQ(Compaction::kInvalidLevel, comp2->GetProximalLevel());
|
||||
}
|
||||
@@ -4333,9 +4392,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
ASSERT_OK(universal_compaction_picker.GetCompactionInputsFromFileNumbers(
|
||||
&input_files, &input_set, vstorage_.get(), comp_options));
|
||||
|
||||
std::unique_ptr<Compaction> comp1(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp1(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 5, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
|
||||
ASSERT_TRUE(comp1);
|
||||
ASSERT_EQ(comp1->GetProximalLevel(), Compaction::kInvalidLevel);
|
||||
@@ -4353,9 +4413,10 @@ TEST_P(PerKeyPlacementCompactionPickerTest,
|
||||
ioptions_, 6, 6)));
|
||||
|
||||
// 2 compactions can be run in parallel
|
||||
std::unique_ptr<Compaction> comp2(universal_compaction_picker.CompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
std::unique_ptr<Compaction> comp2(
|
||||
universal_compaction_picker.PickCompactionForCompactFiles(
|
||||
comp_options, input_files, 6, vstorage_.get(), mutable_cf_options_,
|
||||
mutable_db_options_, 0));
|
||||
ASSERT_TRUE(comp2);
|
||||
if (enable_per_key_placement_) {
|
||||
ASSERT_NE(Compaction::kInvalidLevel, comp2->GetProximalLevel());
|
||||
|
||||
@@ -38,7 +38,8 @@ class UniversalCompactionBuilder {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
UniversalCompactionPicker* picker, LogBuffer* log_buffer)
|
||||
UniversalCompactionPicker* picker, LogBuffer* log_buffer,
|
||||
bool require_max_output_level)
|
||||
: ioptions_(ioptions),
|
||||
icmp_(icmp),
|
||||
cf_name_(cf_name),
|
||||
@@ -46,7 +47,10 @@ class UniversalCompactionBuilder {
|
||||
mutable_db_options_(mutable_db_options),
|
||||
vstorage_(vstorage),
|
||||
picker_(picker),
|
||||
log_buffer_(log_buffer) {
|
||||
log_buffer_(log_buffer),
|
||||
require_max_output_level_(require_max_output_level),
|
||||
allow_ingest_behind_(ioptions.cf_allow_ingest_behind ||
|
||||
ioptions.allow_ingest_behind) {
|
||||
assert(icmp_);
|
||||
const auto* ucmp = icmp_->user_comparator();
|
||||
assert(ucmp);
|
||||
@@ -102,6 +106,174 @@ class UniversalCompactionBuilder {
|
||||
bool level_has_marked_standalone_rangedel;
|
||||
};
|
||||
|
||||
unsigned int GetMaxNumFilesToCompactBasedOnMaxReadAmp(
|
||||
const int file_num_compaction_trigger, const unsigned int ratio,
|
||||
int* num_sr_not_compacted_output, int* max_num_runs_output) const {
|
||||
assert(num_sr_not_compacted_output);
|
||||
assert(max_num_runs_output);
|
||||
int max_num_runs =
|
||||
mutable_cf_options_.compaction_options_universal.max_read_amp;
|
||||
if (max_num_runs < 0) {
|
||||
// any value < -1 is not valid
|
||||
assert(max_num_runs == -1);
|
||||
// By default, fall back to `level0_file_num_compaction_trigger`
|
||||
max_num_runs = file_num_compaction_trigger;
|
||||
} else if (max_num_runs == 0) {
|
||||
if (mutable_cf_options_.compaction_options_universal.stop_style ==
|
||||
kCompactionStopStyleTotalSize) {
|
||||
// 0 means auto-tuning by RocksDB. We estimate max num run based on
|
||||
// max_run_size, size_ratio and write buffer size:
|
||||
// Assume the size of the lowest level size is equal to
|
||||
// write_buffer_size. Each subsequent level is the max size without
|
||||
// triggering size_ratio compaction. `max_num_runs` is the minimum
|
||||
// number of levels required such that the target size of the
|
||||
// largest level is at least `max_run_size_`.
|
||||
max_num_runs = 1;
|
||||
double cur_level_max_size =
|
||||
static_cast<double>(mutable_cf_options_.write_buffer_size);
|
||||
double total_run_size = 0;
|
||||
while (cur_level_max_size < static_cast<double>(max_run_size_)) {
|
||||
// This loop should not take too many iterations since
|
||||
// cur_level_max_size at least doubles each iteration.
|
||||
total_run_size += cur_level_max_size;
|
||||
cur_level_max_size = (100.0 + ratio) / 100.0 * total_run_size;
|
||||
++max_num_runs;
|
||||
}
|
||||
} else {
|
||||
// TODO: implement the auto-tune logic for this stop style
|
||||
max_num_runs = file_num_compaction_trigger;
|
||||
}
|
||||
} else {
|
||||
// max_num_runs > 0, it's the limit on the number of sorted run
|
||||
}
|
||||
|
||||
// Get the total number of sorted runs that are not being compacted
|
||||
int num_sr_not_compacted = 0;
|
||||
for (size_t i = 0; i < sorted_runs_.size(); i++) {
|
||||
if (sorted_runs_[i].being_compacted == false &&
|
||||
!sorted_runs_[i].level_has_marked_standalone_rangedel) {
|
||||
num_sr_not_compacted++;
|
||||
}
|
||||
}
|
||||
|
||||
*num_sr_not_compacted_output = num_sr_not_compacted;
|
||||
*max_num_runs_output = max_num_runs;
|
||||
|
||||
if (num_sr_not_compacted > max_num_runs) {
|
||||
return num_sr_not_compacted - max_num_runs + 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Compaction* MaybePickPeriodicCompaction(Compaction* const prev_picked_c) {
|
||||
if (prev_picked_c != nullptr ||
|
||||
vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
// Always need to do a full compaction for periodic compaction.
|
||||
Compaction* c = PickPeriodicCompaction();
|
||||
TEST_SYNC_POINT_CALLBACK("PostPickPeriodicCompaction", c);
|
||||
if (c != nullptr) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: picked for periodic compaction\n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* MaybePickSizeAmpCompaction(Compaction* const prev_picked_c,
|
||||
int file_num_compaction_trigger) {
|
||||
if (prev_picked_c != nullptr ||
|
||||
sorted_runs_.size() <
|
||||
static_cast<size_t>(file_num_compaction_trigger)) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
Compaction* c = PickCompactionToReduceSizeAmp();
|
||||
if (c != nullptr) {
|
||||
TEST_SYNC_POINT("PickCompactionToReduceSizeAmpReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: picked for size amp compaction \n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* MaybePickCompactionToReduceSortedRunsBasedFileRatio(
|
||||
Compaction* const prev_picked_c, int file_num_compaction_trigger,
|
||||
unsigned int ratio) {
|
||||
if (prev_picked_c != nullptr ||
|
||||
sorted_runs_.size() <
|
||||
static_cast<size_t>(file_num_compaction_trigger)) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
Compaction* c = PickCompactionToReduceSortedRuns(ratio, UINT_MAX);
|
||||
if (c != nullptr) {
|
||||
TEST_SYNC_POINT("PickCompactionToReduceSortedRunsReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: picked for size ratio compaction to "
|
||||
"reduce sorted run\n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* MaybePickCompactionToReduceSortedRuns(
|
||||
Compaction* const prev_picked_c, int file_num_compaction_trigger,
|
||||
unsigned int ratio) {
|
||||
if (prev_picked_c != nullptr ||
|
||||
sorted_runs_.size() <
|
||||
static_cast<size_t>(file_num_compaction_trigger)) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
|
||||
int num_sr_not_compacted = 0;
|
||||
int max_num_runs = 0;
|
||||
const unsigned int max_num_files_to_compact =
|
||||
GetMaxNumFilesToCompactBasedOnMaxReadAmp(file_num_compaction_trigger,
|
||||
ratio, &num_sr_not_compacted,
|
||||
&max_num_runs);
|
||||
if (max_num_files_to_compact == 0) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: skipping compaction to reduce sorted run, num "
|
||||
"sorted runs not "
|
||||
"being compacted -- %u, max num runs allowed -- %d, max_run_size "
|
||||
"-- %" PRIu64 "\n",
|
||||
cf_name_.c_str(), num_sr_not_compacted, max_num_runs, max_run_size_);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Compaction* c =
|
||||
PickCompactionToReduceSortedRuns(UINT_MAX, max_num_files_to_compact);
|
||||
if (c != nullptr) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: picked for sorted run num compaction "
|
||||
"to reduce sorted run, to "
|
||||
"compact file num -- %u, max num runs allowed"
|
||||
"-- %d, max_run_size -- %" PRIu64 "\n",
|
||||
cf_name_.c_str(), max_num_files_to_compact, max_num_runs,
|
||||
max_run_size_);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* MaybePickDeleteTriggeredCompaction(
|
||||
Compaction* const prev_picked_c) {
|
||||
if (prev_picked_c != nullptr) {
|
||||
return prev_picked_c;
|
||||
}
|
||||
Compaction* c = PickDeleteTriggeredCompaction();
|
||||
if (c != nullptr) {
|
||||
TEST_SYNC_POINT("PickDeleteTriggeredCompactionReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: picked for delete triggered compaction\n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// Pick Universal compaction to limit read amplification
|
||||
Compaction* PickCompactionToReduceSortedRuns(
|
||||
unsigned int ratio, unsigned int max_number_of_files_to_compact);
|
||||
@@ -249,6 +421,12 @@ class UniversalCompactionBuilder {
|
||||
return num_l0_to_exclude;
|
||||
}
|
||||
|
||||
bool MeetsOutputLevelRequirements(int output_level) const {
|
||||
return !require_max_output_level_ ||
|
||||
Compaction::OutputToNonZeroMaxOutputLevel(
|
||||
output_level, vstorage_->MaxOutputLevel(allow_ingest_behind_));
|
||||
}
|
||||
|
||||
const ImmutableOptions& ioptions_;
|
||||
const InternalKeyComparator* icmp_;
|
||||
double score_;
|
||||
@@ -270,6 +448,8 @@ class UniversalCompactionBuilder {
|
||||
// marked for compaction. This is only populated when snapshot info is
|
||||
// populated.
|
||||
std::map<uint64_t, size_t> file_marked_for_compaction_to_sorted_run_index_;
|
||||
bool require_max_output_level_;
|
||||
bool allow_ingest_behind_;
|
||||
|
||||
std::vector<UniversalCompactionBuilder::SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, int last_level,
|
||||
@@ -420,10 +600,11 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer, bool require_max_output_level) {
|
||||
UniversalCompactionBuilder builder(
|
||||
ioptions_, icmp_, cf_name, mutable_cf_options, mutable_db_options,
|
||||
existing_snapshots, snapshot_checker, vstorage, this, log_buffer);
|
||||
existing_snapshots, snapshot_checker, vstorage, this, log_buffer,
|
||||
require_max_output_level);
|
||||
return builder.PickCompaction();
|
||||
}
|
||||
|
||||
@@ -554,13 +735,20 @@ bool UniversalCompactionBuilder::ShouldSkipMarkedFile(
|
||||
Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
const int kLevel0 = 0;
|
||||
score_ = vstorage_->CompactionScore(kLevel0);
|
||||
int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
const int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_);
|
||||
const int file_num_compaction_trigger =
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger;
|
||||
const unsigned int ratio =
|
||||
mutable_cf_options_.compaction_options_universal.size_ratio;
|
||||
|
||||
if (max_output_level == 0 &&
|
||||
!MeetsOutputLevelRequirements(0 /* output_level */)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
max_run_size_ = 0;
|
||||
sorted_runs_ =
|
||||
CalculateSortedRuns(*vstorage_, max_output_level, &max_run_size_);
|
||||
int file_num_compaction_trigger =
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger;
|
||||
|
||||
if (sorted_runs_.size() == 0 ||
|
||||
(vstorage_->FilesMarkedForPeriodicCompaction().empty() &&
|
||||
@@ -572,6 +760,7 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
"UniversalCompactionBuilder::PickCompaction:Return", nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
ROCKS_LOG_BUFFER_MAX_SZ(
|
||||
log_buffer_, 3072,
|
||||
@@ -579,127 +768,22 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
cf_name_.c_str(), sorted_runs_.size(), vstorage_->LevelSummary(&tmp));
|
||||
|
||||
Compaction* c = nullptr;
|
||||
// Periodic compaction has higher priority than other type of compaction
|
||||
// because it's a hard requirement.
|
||||
if (!vstorage_->FilesMarkedForPeriodicCompaction().empty()) {
|
||||
// Always need to do a full compaction for periodic compaction.
|
||||
c = PickPeriodicCompaction();
|
||||
TEST_SYNC_POINT_CALLBACK("PostPickPeriodicCompaction", c);
|
||||
}
|
||||
|
||||
if (c == nullptr &&
|
||||
sorted_runs_.size() >= static_cast<size_t>(file_num_compaction_trigger)) {
|
||||
// Check for size amplification.
|
||||
if ((c = PickCompactionToReduceSizeAmp()) != nullptr) {
|
||||
TEST_SYNC_POINT("PickCompactionToReduceSizeAmpReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_, "[%s] Universal: compacting for size amp\n",
|
||||
cf_name_.c_str());
|
||||
} else {
|
||||
// Size amplification is within limits. Try reducing read
|
||||
// amplification while maintaining file size ratios.
|
||||
unsigned int ratio =
|
||||
mutable_cf_options_.compaction_options_universal.size_ratio;
|
||||
|
||||
if ((c = PickCompactionToReduceSortedRuns(ratio, UINT_MAX)) != nullptr) {
|
||||
TEST_SYNC_POINT("PickCompactionToReduceSortedRunsReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: compacting for size ratio\n",
|
||||
cf_name_.c_str());
|
||||
} else {
|
||||
// Size amplification and file size ratios are within configured limits.
|
||||
// If max read amplification exceeds configured limits, then force
|
||||
// compaction to reduce the number sorted runs without looking at file
|
||||
// size ratios.
|
||||
|
||||
// This is guaranteed by NeedsCompaction()
|
||||
assert(sorted_runs_.size() >=
|
||||
static_cast<size_t>(file_num_compaction_trigger));
|
||||
int max_num_runs =
|
||||
mutable_cf_options_.compaction_options_universal.max_read_amp;
|
||||
if (max_num_runs < 0) {
|
||||
// any value < -1 is not valid
|
||||
assert(max_num_runs == -1);
|
||||
// By default, fall back to `level0_file_num_compaction_trigger`
|
||||
max_num_runs = file_num_compaction_trigger;
|
||||
} else if (max_num_runs == 0) {
|
||||
if (mutable_cf_options_.compaction_options_universal.stop_style ==
|
||||
kCompactionStopStyleTotalSize) {
|
||||
// 0 means auto-tuning by RocksDB. We estimate max num run based on
|
||||
// max_run_size, size_ratio and write buffer size:
|
||||
// Assume the size of the lowest level size is equal to
|
||||
// write_buffer_size. Each subsequent level is the max size without
|
||||
// triggering size_ratio compaction. `max_num_runs` is the minimum
|
||||
// number of levels required such that the target size of the
|
||||
// largest level is at least `max_run_size_`.
|
||||
max_num_runs = 1;
|
||||
double cur_level_max_size =
|
||||
static_cast<double>(mutable_cf_options_.write_buffer_size);
|
||||
double total_run_size = 0;
|
||||
while (cur_level_max_size < static_cast<double>(max_run_size_)) {
|
||||
// This loop should not take too many iterations since
|
||||
// cur_level_max_size at least doubles each iteration.
|
||||
total_run_size += cur_level_max_size;
|
||||
cur_level_max_size = (100.0 + ratio) / 100.0 * total_run_size;
|
||||
++max_num_runs;
|
||||
}
|
||||
} else {
|
||||
// TODO: implement the auto-tune logic for this stop style
|
||||
max_num_runs = file_num_compaction_trigger;
|
||||
}
|
||||
} else {
|
||||
// max_num_runs > 0, it's the limit on the number of sorted run
|
||||
}
|
||||
// Get the total number of sorted runs that are not being compacted
|
||||
int num_sr_not_compacted = 0;
|
||||
for (size_t i = 0; i < sorted_runs_.size(); i++) {
|
||||
if (sorted_runs_[i].being_compacted == false &&
|
||||
!sorted_runs_[i].level_has_marked_standalone_rangedel) {
|
||||
num_sr_not_compacted++;
|
||||
}
|
||||
}
|
||||
|
||||
// The number of sorted runs that are not being compacted is greater
|
||||
// than the maximum allowed number of sorted runs
|
||||
if (num_sr_not_compacted > max_num_runs) {
|
||||
unsigned int num_files = num_sr_not_compacted - max_num_runs + 1;
|
||||
if ((c = PickCompactionToReduceSortedRuns(UINT_MAX, num_files)) !=
|
||||
nullptr) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: compacting for file num, to "
|
||||
"compact file num -- %u, max num runs allowed"
|
||||
"-- %d, max_run_size -- %" PRIu64 "\n",
|
||||
cf_name_.c_str(), num_files, max_num_runs,
|
||||
max_run_size_);
|
||||
}
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: skipping compaction for file num, num runs not "
|
||||
"being compacted -- %u, max num runs allowed -- %d, max_run_size "
|
||||
"-- %" PRIu64 "\n",
|
||||
cf_name_.c_str(), num_sr_not_compacted, max_num_runs,
|
||||
max_run_size_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (c == nullptr) {
|
||||
if ((c = PickDeleteTriggeredCompaction()) != nullptr) {
|
||||
TEST_SYNC_POINT("PickDeleteTriggeredCompactionReturnNonnullptr");
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: delete triggered compaction\n",
|
||||
cf_name_.c_str());
|
||||
}
|
||||
}
|
||||
c = MaybePickPeriodicCompaction(c);
|
||||
c = MaybePickSizeAmpCompaction(c, file_num_compaction_trigger);
|
||||
c = MaybePickCompactionToReduceSortedRunsBasedFileRatio(
|
||||
c, file_num_compaction_trigger, ratio);
|
||||
c = MaybePickCompactionToReduceSortedRuns(c, file_num_compaction_trigger,
|
||||
ratio);
|
||||
c = MaybePickDeleteTriggeredCompaction(c);
|
||||
|
||||
if (c == nullptr) {
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"UniversalCompactionBuilder::PickCompaction:Return", nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
assert(c->output_level() <=
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
|
||||
assert(c->output_level() <= vstorage_->MaxOutputLevel(allow_ingest_behind_));
|
||||
assert(MeetsOutputLevelRequirements(c->output_level()));
|
||||
|
||||
if (mutable_cf_options_.compaction_options_universal.allow_trivial_move ==
|
||||
true &&
|
||||
@@ -825,14 +909,16 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
if (sr->being_compacted) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] being compacted, skipping",
|
||||
"[%d] being compacted, skipping for compaction to "
|
||||
"reduce sorted runs",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
} else if (sr->level_has_marked_standalone_rangedel) {
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] has standalone range tombstone files marked for "
|
||||
"compaction, skipping",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: %s"
|
||||
"[%d] has standalone range tombstone files marked for "
|
||||
"compaction, skipping for compaction to reduce sorted runs",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
}
|
||||
|
||||
sr = nullptr;
|
||||
@@ -845,7 +931,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: Possible candidate %s[%d].",
|
||||
"[%s] Universal: Possible candidate for compaction to "
|
||||
"reduce sorted runs %s[%d].",
|
||||
cf_name_.c_str(), file_num_buf, loop);
|
||||
}
|
||||
|
||||
@@ -937,8 +1024,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
int start_level = sorted_runs_[start_index].level;
|
||||
int output_level;
|
||||
// last level is reserved for the files ingested behind
|
||||
int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_);
|
||||
if (first_index_after == sorted_runs_.size()) {
|
||||
output_level = max_output_level;
|
||||
} else if (sorted_runs_[first_index_after].level == 0) {
|
||||
@@ -947,6 +1033,10 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
output_level = sorted_runs_[first_index_after].level - 1;
|
||||
}
|
||||
|
||||
if (!MeetsOutputLevelRequirements(output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(max_output_level + 1);
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
inputs[i].level = start_level + static_cast<int>(i);
|
||||
@@ -1003,13 +1093,12 @@ 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,
|
||||
/* is manual */ false, /* trim_ts */ "", score_,
|
||||
false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true, compaction_reason);
|
||||
/* snapshot_checker */ nullptr, compaction_reason,
|
||||
/* trim_ts */ "", score_,
|
||||
/* l0_files_might_overlap */ true);
|
||||
}
|
||||
|
||||
// Look at overall size amplification. If size amplification
|
||||
@@ -1039,18 +1128,19 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
if (sr->being_compacted) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run undergoing compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: stopping for size amp compaction at "
|
||||
"sorted run undergoing compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
} else if (sr->level_has_marked_standalone_rangedel) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: stopping at sorted run that has standalone range "
|
||||
"tombstone files marked for compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: stopping for size amp compaction at "
|
||||
"sorted run that has "
|
||||
"standalone range "
|
||||
"tombstone files marked for compaction: "
|
||||
"%s[%" ROCKSDB_PRIszt "]",
|
||||
cf_name_.c_str(), file_num_buf, start_index - 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1066,11 +1156,12 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
{
|
||||
const size_t num_l0_to_exclude = MightExcludeNewL0sToReduceWriteStop(
|
||||
num_l0_files, end_index, start_index, candidate_size);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: Excluding %" ROCKSDB_PRIszt
|
||||
" latest L0 files to reduce potential write stop "
|
||||
"triggered by `level0_stop_writes_trigger`",
|
||||
cf_name_.c_str(), num_l0_to_exclude);
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: Excluding for size amp compaction %" ROCKSDB_PRIszt
|
||||
" latest L0 files to reduce potential write stop "
|
||||
"triggered by `level0_stop_writes_trigger`",
|
||||
cf_name_.c_str(), num_l0_to_exclude);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1088,18 +1179,18 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
|
||||
// size amplification = percentage of additional size
|
||||
if (candidate_size * 100 < ratio * base_sr_size) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: size amp not needed. newer-files-total-size %" PRIu64
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, base_sr_size);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: size amp compction not needed. "
|
||||
"newer-files-total-size %" PRIu64
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, base_sr_size);
|
||||
return nullptr;
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer_,
|
||||
"[%s] Universal: size amp needed. newer-files-total-size %" PRIu64
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, base_sr_size);
|
||||
ROCKS_LOG_BUFFER(log_buffer_,
|
||||
"[%s] Universal: size amp compaction needed. "
|
||||
"newer-files-total-size %" PRIu64
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, base_sr_size);
|
||||
}
|
||||
// Since incremental compaction can't include more than second last
|
||||
// level, it can introduce penalty, compared to full compaction. We
|
||||
@@ -1350,14 +1441,13 @@ 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,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
CompactionReason::kUniversalSizeAmplification,
|
||||
/* trim_ts */ "", score_,
|
||||
/* l0_files_might_overlap */ true);
|
||||
}
|
||||
|
||||
// Pick files marked for compaction. Typically, files are marked by
|
||||
@@ -1426,8 +1516,7 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_);
|
||||
// Pick the first non-empty level after the start_level
|
||||
for (output_level = start_level + 1; output_level <= max_output_level;
|
||||
output_level++) {
|
||||
@@ -1450,6 +1539,10 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
}
|
||||
assert(output_level <= max_output_level);
|
||||
|
||||
if (!MeetsOutputLevelRequirements(output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (output_level != 0) {
|
||||
if (start_level == 0) {
|
||||
if (!picker_->GetOverlappingL0Files(vstorage_, &start_level_inputs,
|
||||
@@ -1501,13 +1594,11 @@ 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_,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
snapshot_checker_, CompactionReason::kFilesMarkedForCompaction,
|
||||
/* trim_ts */ "", score_,
|
||||
/* l0_files_might_overlap */ true);
|
||||
}
|
||||
|
||||
Compaction* UniversalCompactionBuilder::PickCompactionToOldest(
|
||||
@@ -1528,8 +1619,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
uint32_t path_id =
|
||||
GetPathId(ioptions_, mutable_cf_options_, estimated_total_size);
|
||||
int start_level = sorted_runs_[start_index].level;
|
||||
int max_output_level =
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind);
|
||||
int max_output_level = vstorage_->MaxOutputLevel(allow_ingest_behind_);
|
||||
std::vector<CompactionInputFiles> inputs(max_output_level + 1);
|
||||
for (size_t i = 0; i < inputs.size(); ++i) {
|
||||
inputs[i].level = start_level + static_cast<int>(i);
|
||||
@@ -1574,6 +1664,10 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
output_level = sorted_runs_[end_index + 1].level - 1;
|
||||
}
|
||||
|
||||
if (!MeetsOutputLevelRequirements(output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// intra L0 compactions outputs could have overlap
|
||||
if (output_level != 0 && picker_->FilesRangeOverlapWithCompaction(
|
||||
inputs, output_level,
|
||||
@@ -1596,13 +1690,12 @@ 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,
|
||||
/* is manual */ false,
|
||||
/* trim_ts */ "", score_, false /* deletion_compaction */,
|
||||
/* l0_files_might_overlap */ true, compaction_reason);
|
||||
/* snapshot_checker */ nullptr, compaction_reason,
|
||||
/* trim_ts */ "", score_,
|
||||
/* l0_files_might_overlap */ true);
|
||||
}
|
||||
|
||||
Compaction* UniversalCompactionBuilder::PickPeriodicCompaction() {
|
||||
|
||||
@@ -18,12 +18,15 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
UniversalCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
|
||||
// If `require_max_output_level` is true, only pick compaction
|
||||
// with max output level or return nullptr if no such compaction exists.
|
||||
Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const std::vector<SequenceNumber>& existing_snapshots,
|
||||
const SnapshotChecker* snapshot_checker, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
LogBuffer* log_buffer, bool require_max_output_level = false) override;
|
||||
int MaxOutputLevel() const override { return NumberLevels() - 1; }
|
||||
|
||||
bool NeedsCompaction(const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
@@ -41,7 +41,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
}
|
||||
|
||||
compaction_input.cf_name = compaction->column_family_data()->GetName();
|
||||
compaction_input.snapshots = existing_snapshots_;
|
||||
compaction_input.snapshots = job_context_->snapshot_seqs;
|
||||
compaction_input.has_begin = sub_compact->start.has_value();
|
||||
compaction_input.begin =
|
||||
compaction_input.has_begin ? sub_compact->start->ToString() : "";
|
||||
@@ -74,10 +74,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_,
|
||||
compaction_input.output_level, input_files_oss.str().c_str());
|
||||
CompactionServiceJobInfo info(
|
||||
dbname_, db_id_, db_session_id_, GetCompactionId(sub_compact),
|
||||
dbname_, db_id_, db_session_id_,
|
||||
compaction->column_family_data()->GetID(),
|
||||
compaction->column_family_data()->GetName(), GetCompactionId(sub_compact),
|
||||
thread_pri_, compaction->compaction_reason(),
|
||||
compaction->is_full_compaction(), compaction->is_manual_compaction(),
|
||||
compaction->bottommost_level());
|
||||
compaction->bottommost_level(), compaction->start_level(),
|
||||
compaction->output_level());
|
||||
|
||||
CompactionServiceScheduleResponse response =
|
||||
db_options_.compaction_service->Schedule(info, compaction_input_binary);
|
||||
switch (response.status) {
|
||||
@@ -111,7 +115,7 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
}
|
||||
|
||||
std::string debug_str_before_wait =
|
||||
compaction->input_version()->DebugString();
|
||||
compaction->input_version()->DebugString(/*hex=*/true);
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Waiting for remote compaction...",
|
||||
@@ -122,13 +126,14 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
&compaction_result_binary);
|
||||
|
||||
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
|
||||
ROCKS_LOG_ERROR(db_options_.info_log,
|
||||
"[%s] [JOB %d] Wait() status is not kSuccess. "
|
||||
"\nDebugString Before Wait():\n%s"
|
||||
"\nDebugString After Wait():\n%s",
|
||||
compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_, debug_str_before_wait.c_str(),
|
||||
compaction->input_version()->DebugString().c_str());
|
||||
ROCKS_LOG_ERROR(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Wait() status is not kSuccess. "
|
||||
"\nDebugString Before Wait():\n%s"
|
||||
"\nDebugString After Wait():\n%s",
|
||||
compaction->column_family_data()->GetName().c_str(), job_id_,
|
||||
debug_str_before_wait.c_str(),
|
||||
compaction->input_version()->DebugString(/*hex=*/true).c_str());
|
||||
}
|
||||
|
||||
if (compaction_status == CompactionServiceJobStatus::kUseLocal) {
|
||||
@@ -216,18 +221,24 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
}
|
||||
|
||||
FileMetaData meta;
|
||||
uint64_t file_size;
|
||||
// FIXME: file_size should be part of CompactionServiceOutputFile so that
|
||||
// we don't get DB corruption if the full file size has not been propagated
|
||||
// back to the caller through the file system (which could have metadata
|
||||
// lag or caching bugs).
|
||||
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
|
||||
uint64_t file_size = file.file_size;
|
||||
|
||||
// TODO - Clean this up in the next release.
|
||||
// For backward compatibility - in case the remote worker does not populate
|
||||
// the file_size yet. If missing, continue to populate this from the file
|
||||
// system.
|
||||
if (file_size == 0) {
|
||||
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
db_options_.compaction_service->OnInstallation(
|
||||
response.scheduled_job_id, CompactionServiceJobStatus::kFailure);
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
assert(file_size > 0);
|
||||
|
||||
meta.fd = FileDescriptor(file_num, compaction->output_path_id(), file_size,
|
||||
file.smallest_seqno, file.largest_seqno);
|
||||
meta.smallest.DecodeFrom(file.smallest_internal_key);
|
||||
@@ -240,7 +251,8 @@ CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
meta.marked_for_compaction = file.marked_for_compaction;
|
||||
meta.unique_id = file.unique_id;
|
||||
meta.temperature = file.file_temperature;
|
||||
|
||||
meta.tail_size =
|
||||
FileMetaData::CalculateTailSize(file_size, file.table_properties);
|
||||
auto cfd = compaction->column_family_data();
|
||||
CompactionOutputs* compaction_outputs =
|
||||
sub_compact->Outputs(file.is_proximal_level_output);
|
||||
@@ -292,9 +304,9 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
VersionSet* versions, const std::atomic<bool>* shutting_down,
|
||||
LogBuffer* log_buffer, FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
JobContext* job_context, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, const std::string& dbname,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string output_path,
|
||||
@@ -303,9 +315,8 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
: CompactionJob(job_id, compaction, db_options, mutable_db_options,
|
||||
file_options, versions, shutting_down, log_buffer, nullptr,
|
||||
output_directory, nullptr, stats, db_mutex,
|
||||
db_error_handler, std::move(existing_snapshots),
|
||||
kMaxSequenceNumber, nullptr, nullptr,
|
||||
std::move(table_cache), event_logger,
|
||||
db_error_handler, job_context, std::move(table_cache),
|
||||
event_logger,
|
||||
compaction->mutable_cf_options().paranoid_file_checks,
|
||||
compaction->mutable_cf_options().report_bg_io_stats, dbname,
|
||||
&(compaction_service_result->stats), Env::Priority::USER,
|
||||
@@ -315,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;
|
||||
@@ -324,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() {
|
||||
@@ -397,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();
|
||||
@@ -415,14 +429,14 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
for (const auto& output_file : sub_compact->GetOutputs()) {
|
||||
auto& meta = output_file.meta;
|
||||
compaction_result_->output_files.emplace_back(
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.smallest_seqno,
|
||||
meta.fd.largest_seqno, meta.smallest.Encode().ToString(),
|
||||
meta.largest.Encode().ToString(), meta.oldest_ancester_time,
|
||||
meta.file_creation_time, meta.epoch_number, meta.file_checksum,
|
||||
meta.file_checksum_func_name, output_file.validator.GetHash(),
|
||||
meta.marked_for_compaction, meta.unique_id,
|
||||
*output_file.table_properties, output_file.is_proximal_level,
|
||||
meta.temperature);
|
||||
MakeTableFileName(meta.fd.GetNumber()), meta.fd.GetFileSize(),
|
||||
meta.fd.smallest_seqno, meta.fd.largest_seqno,
|
||||
meta.smallest.Encode().ToString(), meta.largest.Encode().ToString(),
|
||||
meta.oldest_ancester_time, meta.file_creation_time, meta.epoch_number,
|
||||
meta.file_checksum, meta.file_checksum_func_name,
|
||||
output_file.validator.GetHash(), meta.marked_for_compaction,
|
||||
meta.unique_id, *output_file.table_properties,
|
||||
output_file.is_proximal_level, meta.temperature);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -522,6 +536,10 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct CompactionServiceOutputFile, file_name),
|
||||
OptionType::kEncodedString, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"file_size",
|
||||
{offsetof(struct CompactionServiceOutputFile, file_size),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
{"smallest_seqno",
|
||||
{offsetof(struct CompactionServiceOutputFile, smallest_seqno),
|
||||
OptionType::kUInt64T, OptionVerificationType::kNormal,
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
// (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"
|
||||
#include "utilities/merge_operators/string_append/stringappend.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -15,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),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
|
||||
false, false, false),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL, CompactionReason::kUnknown,
|
||||
false, false, false),
|
||||
listeners_(listeners),
|
||||
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_(std::move(listeners)),
|
||||
table_properties_collector_factories_(
|
||||
std::move(table_properties_collector_factories)) {}
|
||||
|
||||
@@ -71,33 +73,14 @@ class MyTestCompactionService : public CompactionService {
|
||||
if (is_override_wait_status_) {
|
||||
return override_wait_status_;
|
||||
}
|
||||
CompactionServiceOptionsOverride options_override;
|
||||
options_override.env = options_.env;
|
||||
options_override.file_checksum_gen_factory =
|
||||
options_.file_checksum_gen_factory;
|
||||
options_override.comparator = options_.comparator;
|
||||
options_override.merge_operator = options_.merge_operator;
|
||||
options_override.compaction_filter = options_.compaction_filter;
|
||||
options_override.compaction_filter_factory =
|
||||
options_.compaction_filter_factory;
|
||||
options_override.prefix_extractor = options_.prefix_extractor;
|
||||
options_override.table_factory = options_.table_factory;
|
||||
options_override.sst_partitioner_factory = options_.sst_partitioner_factory;
|
||||
options_override.statistics = statistics_;
|
||||
if (!listeners_.empty()) {
|
||||
options_override.listeners = listeners_;
|
||||
}
|
||||
|
||||
if (!table_properties_collector_factories_.empty()) {
|
||||
options_override.table_properties_collector_factories =
|
||||
table_properties_collector_factories_;
|
||||
}
|
||||
CompactionServiceOptionsOverride options_override = GetOptionsOverride();
|
||||
|
||||
OpenAndCompactOptions options;
|
||||
options.canceled = &canceled_;
|
||||
|
||||
Status s =
|
||||
DB::OpenAndCompact(options, db_path_, db_path_ + "/" + scheduled_job_id,
|
||||
DB::OpenAndCompact(options, db_path_, GetOutputPath(scheduled_job_id),
|
||||
compaction_input, result, options_override);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -114,6 +97,32 @@ class MyTestCompactionService : public CompactionService {
|
||||
}
|
||||
}
|
||||
|
||||
CompactionServiceOptionsOverride GetOptionsOverride() {
|
||||
CompactionServiceOptionsOverride options_override;
|
||||
options_override.env = options_.env;
|
||||
options_override.file_checksum_gen_factory =
|
||||
options_.file_checksum_gen_factory;
|
||||
options_override.comparator = options_.comparator;
|
||||
options_override.merge_operator = options_.merge_operator;
|
||||
options_override.compaction_filter = options_.compaction_filter;
|
||||
options_override.compaction_filter_factory =
|
||||
options_.compaction_filter_factory;
|
||||
options_override.prefix_extractor = options_.prefix_extractor;
|
||||
options_override.table_factory = options_.table_factory;
|
||||
options_override.sst_partitioner_factory = options_.sst_partitioner_factory;
|
||||
options_override.statistics = statistics_;
|
||||
options_override.info_log = options_.info_log;
|
||||
if (!listeners_.empty()) {
|
||||
options_override.listeners = listeners_;
|
||||
}
|
||||
|
||||
if (!table_properties_collector_factories_.empty()) {
|
||||
options_override.table_properties_collector_factories =
|
||||
table_properties_collector_factories_;
|
||||
}
|
||||
return options_override;
|
||||
}
|
||||
|
||||
void CancelAwaitingJobs() override { canceled_ = true; }
|
||||
|
||||
void OnInstallation(const std::string& /*scheduled_job_id*/,
|
||||
@@ -158,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;
|
||||
@@ -175,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>>
|
||||
@@ -277,8 +292,17 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
Statistics* primary_statistics = GetPrimaryStatistics();
|
||||
Statistics* compactor_statistics = GetCompactorStatistics();
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTable::PrefetchTail::TaiSizeNotRecorded",
|
||||
[&](void* /* arg */) {
|
||||
// Trigger assertion to verify precise tail prefetch size calculation
|
||||
assert(false);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
GenerateTestData();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
VerifyTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
@@ -380,6 +404,7 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ASSERT_FALSE(result.stats.is_full_compaction);
|
||||
|
||||
Close();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
@@ -423,6 +448,133 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
|
||||
auto info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(0, info.cf_id);
|
||||
ASSERT_EQ(kDefaultColumnFamilyName, info.cf_name);
|
||||
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(0, info.cf_id);
|
||||
ASSERT_EQ(kDefaultColumnFamilyName, info.cf_name);
|
||||
|
||||
// Test non-default CF
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr, nullptr));
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_TRUE(result.stats.is_manual_compaction);
|
||||
ASSERT_TRUE(result.stats.is_remote_compaction);
|
||||
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(handles_[1]->GetID(), info.cf_id);
|
||||
ASSERT_EQ(handles_[1]->GetName(), info.cf_name);
|
||||
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(handles_[1]->GetID(), info.cf_id);
|
||||
ASSERT_EQ(handles_[1]->GetName(), info.cf_name);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
size_t num_files_after_filtered = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::MakeInputIterator:NewCompactionMergingIterator",
|
||||
[&](void* arg) {
|
||||
num_files_after_filtered = *static_cast<size_t*>(arg);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (auto compaction_style : {CompactionStyle::kCompactionStyleLevel,
|
||||
CompactionStyle::kCompactionStyleUniversal}) {
|
||||
SCOPED_TRACE("Style: " + std::to_string(compaction_style));
|
||||
options.compaction_style = compaction_style;
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CompactionOutputFileIOError) {
|
||||
@@ -757,6 +909,79 @@ TEST_F(CompactionServiceTest, VerifyInputRecordCount) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, EmptyResult) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
|
||||
// Delete range to cover entire range
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), "key", "keyz"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// In this unit test, both remote compaction and primary db instance are
|
||||
// running in the same process, so NewFileNumber will never have a collision.
|
||||
// In the real-world remote compactions, when the compaction is indeed running
|
||||
// in another process, this is not going to be the case.
|
||||
// To simulate the SST file with the same name created in the tmp directory,
|
||||
// override the file number in remote compaction to re-use old SST file
|
||||
// number.
|
||||
bool need_to_override_file_number = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::OpenAndCompact::BeforeLoadingOptions:0",
|
||||
[&](void*) { need_to_override_file_number = true; });
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::OpenCompactionOutputFile::NewFileNumber",
|
||||
[&](void* file_number) {
|
||||
if (need_to_override_file_number) {
|
||||
auto n = static_cast<uint64_t*>(file_number);
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
for (const auto& level : cf_meta.levels) {
|
||||
for (const auto& file : level.files) {
|
||||
// Use one of the existing file name
|
||||
*n = test::GetFileNumber(file.name);
|
||||
need_to_override_file_number = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Inject failure, so that the remote compaction fails after
|
||||
// ProcessKeyValueCompaction()
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
|
||||
// override job status
|
||||
auto s = static_cast<Status*>(status);
|
||||
*s = Status::Aborted("MyTestCompactionService failed to compact!");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Compaction should fail and SST files in the primary db should exist
|
||||
{
|
||||
ASSERT_NOK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
for (const auto& level : meta.levels) {
|
||||
for (const auto& file : level.files) {
|
||||
std::string fname = file.db_path + "/" + file.name;
|
||||
ASSERT_OK(db_->GetEnv()->FileExists(fname));
|
||||
}
|
||||
}
|
||||
}
|
||||
Close();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CorruptedOutput) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -822,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();
|
||||
|
||||
@@ -876,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;
|
||||
@@ -890,6 +1197,12 @@ TEST_F(CompactionServiceTest, TruncatedOutput) {
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
// Skip calculating tail size to avoid crashing due to truncated file size
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"FileMetaData::CalculateTailSize", [&](void* arg) {
|
||||
bool* skip = static_cast<bool*>(arg);
|
||||
*skip = true;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionServiceCompactionJob::Run:0", [&](void* arg) {
|
||||
CompactionServiceResult* compaction_result =
|
||||
@@ -906,7 +1219,7 @@ TEST_F(CompactionServiceTest, TruncatedOutput) {
|
||||
ASSERT_OK(s);
|
||||
ASSERT_GT(file_size, 0);
|
||||
|
||||
ASSERT_OK(test::TruncateFile(env_, file_name, file_size / 2));
|
||||
ASSERT_OK(test::TruncateFile(env_, file_name, file_size / 4));
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -1181,6 +1494,32 @@ TEST_F(CompactionServiceTest, CompactionFilter) {
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, MergeOperator) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator.reset(new StringAppendOperator(','));
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
for (int i = 0; i < 200; i++) {
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Key(i),
|
||||
"merge_op_append_" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// verify result
|
||||
for (int i = 0; i < 200; i++) {
|
||||
auto result = Get(Key(i));
|
||||
if (i % 2) {
|
||||
ASSERT_EQ(result, "value" + std::to_string(i) + ",merge_op_append_" +
|
||||
std::to_string(i));
|
||||
} else {
|
||||
ASSERT_EQ(result, "value_new" + std::to_string(i) + ",merge_op_append_" +
|
||||
std::to_string(i));
|
||||
}
|
||||
}
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, Snapshot) {
|
||||
Options options = CurrentOptions();
|
||||
ReopenWithCompactionService(&options);
|
||||
@@ -1250,17 +1589,31 @@ TEST_F(CompactionServiceTest, PrecludeLastLevel) {
|
||||
|
||||
// Verify Output Stats
|
||||
auto my_cs = GetCompactionService();
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_GT(result.internal_stats.output_level_stats.cpu_micros, 0);
|
||||
ASSERT_GT(result.internal_stats.output_level_stats.micros, 0);
|
||||
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_records +
|
||||
result.internal_stats.proximal_level_stats.num_output_records,
|
||||
kNumTrigger * kNumKeys);
|
||||
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_files +
|
||||
result.internal_stats.proximal_level_stats.num_output_files,
|
||||
2);
|
||||
{
|
||||
CompactionServiceResult result;
|
||||
my_cs->GetResult(&result);
|
||||
ASSERT_OK(result.status);
|
||||
ASSERT_GT(result.internal_stats.output_level_stats.cpu_micros, 0);
|
||||
ASSERT_GT(result.internal_stats.output_level_stats.micros, 0);
|
||||
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_records +
|
||||
result.internal_stats.proximal_level_stats.num_output_records,
|
||||
kNumTrigger * kNumKeys);
|
||||
ASSERT_EQ(result.internal_stats.output_level_stats.num_output_files +
|
||||
result.internal_stats.proximal_level_stats.num_output_files,
|
||||
2);
|
||||
|
||||
CompactionServiceJobInfo info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(0, info.base_input_level);
|
||||
ASSERT_EQ(kNumLevels - 1, info.output_level);
|
||||
}
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
// Disable Preclude feature and run full compaction to the bottommost level
|
||||
{
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
CompactionServiceJobInfo info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(kNumLevels - 2, info.base_input_level);
|
||||
ASSERT_EQ(kNumLevels - 1, info.output_level);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, ConcurrentCompaction) {
|
||||
@@ -1330,12 +1683,17 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(1, info.base_input_level);
|
||||
ASSERT_EQ(2, info.output_level);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(1, info.base_input_level);
|
||||
ASSERT_EQ(2, info.output_level);
|
||||
ASSERT_EQ(kDefaultColumnFamilyName, info.cf_name);
|
||||
|
||||
// Test priority BOTTOM
|
||||
env_->SetBackgroundThreads(1, Env::BOTTOM);
|
||||
@@ -1367,18 +1725,24 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
ASSERT_EQ(0, info.base_input_level);
|
||||
ASSERT_EQ(db_->NumberLevels() - 1, info.output_level);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
ASSERT_EQ(CompactionReason::kLevelL0FilesNum, info.compaction_reason);
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(0, info.base_input_level);
|
||||
ASSERT_EQ(db_->NumberLevels() - 1, info.output_level);
|
||||
|
||||
// Test Non-Bottommost Level
|
||||
options.num_levels = 4;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
int compaction_num = my_cs->GetCompactionNum();
|
||||
ASSERT_EQ(0, compaction_num);
|
||||
|
||||
for (int i = 0; i < options.level0_file_num_compaction_trigger; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
@@ -1387,16 +1751,22 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// This is trivial move. Done locally.
|
||||
ASSERT_EQ(0, my_cs->GetCompactionNum());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(false, info.bottommost_level);
|
||||
ASSERT_EQ(-1, info.base_input_level);
|
||||
ASSERT_EQ(-1, info.output_level);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(false, info.is_manual_compaction);
|
||||
ASSERT_EQ(false, info.is_full_compaction);
|
||||
ASSERT_EQ(false, info.bottommost_level);
|
||||
ASSERT_EQ(-1, info.base_input_level);
|
||||
ASSERT_EQ(-1, info.output_level);
|
||||
|
||||
// Test Full Compaction + Bottommost Level
|
||||
options.num_levels = 6;
|
||||
@@ -1411,7 +1781,10 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
MoveFilesToLevel(options.num_levels - 1);
|
||||
|
||||
// Force final level compaction
|
||||
// base_input_level == output_level == last_level
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
@@ -1423,10 +1796,15 @@ TEST_F(CompactionServiceTest, CompactionInfo) {
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(options.num_levels - 1, info.base_input_level);
|
||||
ASSERT_EQ(options.num_levels - 1, info.output_level);
|
||||
ASSERT_EQ(true, info.is_manual_compaction);
|
||||
ASSERT_EQ(true, info.is_full_compaction);
|
||||
ASSERT_EQ(true, info.bottommost_level);
|
||||
ASSERT_EQ(CompactionReason::kManualCompaction, info.compaction_reason);
|
||||
ASSERT_EQ(options.num_levels - 1, info.base_input_level);
|
||||
ASSERT_EQ(options.num_levels - 1, info.output_level);
|
||||
ASSERT_EQ("0,0,0,0,0,1", FilesPerLevel());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, FallbackLocalAuto) {
|
||||
@@ -1722,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
|
||||
@@ -106,7 +106,11 @@ class SubcompactionState {
|
||||
subcompaction_job_info.subcompaction_job_id = static_cast<int>(sub_job_id);
|
||||
subcompaction_job_info.base_input_level = c->start_level();
|
||||
subcompaction_job_info.output_level = c->output_level();
|
||||
subcompaction_job_info.compaction_reason = c->compaction_reason();
|
||||
subcompaction_job_info.compression = c->output_compression();
|
||||
subcompaction_job_info.stats = compaction_job_stats;
|
||||
subcompaction_job_info.blob_compression_type =
|
||||
c->mutable_cf_options().blob_compression_type;
|
||||
}
|
||||
|
||||
SubcompactionState() = delete;
|
||||
@@ -187,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();
|
||||
}
|
||||
@@ -196,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.
|
||||
@@ -229,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
|
||||
@@ -225,6 +225,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageUniversal) {
|
||||
flush_stats.micros = 1;
|
||||
flush_stats.bytes_written = bytes_per_file;
|
||||
flush_stats.num_output_files = 1;
|
||||
flush_stats.num_input_records = kNumKeys;
|
||||
flush_stats.num_output_records = kNumKeys;
|
||||
expect_stats[0].Add(flush_stats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
@@ -1080,6 +1082,8 @@ TEST_F(TieredCompactionTest, SequenceBasedTieredStorageLevel) {
|
||||
flush_stats.micros = 1;
|
||||
flush_stats.bytes_written = bytes_per_file;
|
||||
flush_stats.num_output_files = 1;
|
||||
flush_stats.num_input_records = kNumKeys;
|
||||
flush_stats.num_output_records = kNumKeys;
|
||||
expect_stats[0].Add(flush_stats);
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
@@ -1760,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);
|
||||
@@ -1787,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
|
||||
@@ -1795,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();
|
||||
}
|
||||
|
||||
+4
-3
@@ -93,9 +93,10 @@ Status VerifySstFileChecksumInternal(const Options& options,
|
||||
nullptr /* file_read_hist */, ioptions.rate_limiter.get()));
|
||||
const bool kImmortal = true;
|
||||
auto reader_options = TableReaderOptions(
|
||||
ioptions, options.prefix_extractor, env_options, internal_comparator,
|
||||
options.block_protection_bytes_per_key, false /* skip_filters */,
|
||||
!kImmortal, false /* force_direct_prefetch */, -1 /* level */);
|
||||
ioptions, options.prefix_extractor, options.compression_manager.get(),
|
||||
env_options, internal_comparator, options.block_protection_bytes_per_key,
|
||||
false /* skip_filters */, !kImmortal, false /* force_direct_prefetch */,
|
||||
-1 /* level */);
|
||||
reader_options.largest_seqno = largest_seqno;
|
||||
s = options.table_factory->NewTableReader(
|
||||
read_options, reader_options, std::move(file_reader), file_size,
|
||||
|
||||
+74
-6
@@ -556,6 +556,74 @@ TEST_F(CorruptionTest, TableFileFooterNotMagic) {
|
||||
ASSERT_TRUE(s.ToString().find(".sst") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, DBOpenWithWrongFileSize) {
|
||||
// Validate that when paranoid flag is true, DB::Open() fails if one of the
|
||||
// file corrupted. Validate that when paranoid flag is false, DB::Open()
|
||||
// succeed if one of the file corrupted, and the healthy file is readable.
|
||||
CloseDb();
|
||||
|
||||
const std::string test_cf_name = "test_cf";
|
||||
std::vector<ColumnFamilyDescriptor> cf_descs;
|
||||
cf_descs.emplace_back(kDefaultColumnFamilyName, ColumnFamilyOptions());
|
||||
cf_descs.emplace_back(test_cf_name, ColumnFamilyOptions());
|
||||
|
||||
{
|
||||
options_.create_missing_column_families = true;
|
||||
std::vector<ColumnFamilyHandle*> cfhs;
|
||||
ASSERT_OK(DB::Open(options_, dbname_, cf_descs, &cfhs, &db_));
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
|
||||
ASSERT_OK(db_->Put(WriteOptions(), cfhs[0], "k", "v"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), cfhs[1], "k1", "v1"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), cfhs[0], "k2", "v2"));
|
||||
for (auto* cfh : cfhs) {
|
||||
delete cfh;
|
||||
}
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
ASSERT_OK(dbi->TEST_FlushMemTable());
|
||||
|
||||
// ********************************************
|
||||
// Corrupt the file by making the file bigger
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
std::string filename = dbname_ + metadata[0].name;
|
||||
const auto& fs = options_.env->GetFileSystem();
|
||||
{
|
||||
std::unique_ptr<FSWritableFile> f;
|
||||
ASSERT_OK(fs->ReopenWritableFile(filename, FileOptions(), &f, nullptr));
|
||||
ASSERT_OK(f->Append("blahblah", IOOptions(), nullptr));
|
||||
ASSERT_OK(f->Close(IOOptions(), nullptr));
|
||||
}
|
||||
CloseDb();
|
||||
}
|
||||
|
||||
// DB failed to open due to one of the file is corrupted, as paranoid flag is
|
||||
// true
|
||||
options_.paranoid_checks = true;
|
||||
std::vector<ColumnFamilyHandle*> cfhs;
|
||||
auto s = DB::Open(options_, dbname_, cf_descs, &cfhs, &db_);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(s.ToString().find("file size mismatch") != std::string::npos);
|
||||
|
||||
// DB opened successfully, as paranoid flag is false, validate the one that is
|
||||
// healthy is still accessible
|
||||
options_.paranoid_checks = false;
|
||||
ASSERT_OK(DB::Open(options_, dbname_, cf_descs, &cfhs, &db_));
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
|
||||
std::string v;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), cfhs[1], "k1", &v));
|
||||
ASSERT_EQ(v, "v1");
|
||||
|
||||
// Validate the default column family is corrupted
|
||||
Check(0, 0);
|
||||
s = db_->Get(ReadOptions(), cfhs[0], "k1", &v);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
delete cfhs[1];
|
||||
delete cfhs[0];
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, TableFileWrongSize) {
|
||||
Build(100);
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
@@ -579,13 +647,16 @@ TEST_F(CorruptionTest, TableFileWrongSize) {
|
||||
// DB actually accepts this without paranoid checks, relying on size
|
||||
// recorded in manifest to locate the SST footer.
|
||||
options_.paranoid_checks = false;
|
||||
options_.skip_checking_sst_file_sizes_on_db_open = false;
|
||||
Reopen();
|
||||
Check(100, 100);
|
||||
// As footer could not be extraced, file is completely unreadable
|
||||
Check(0, 0);
|
||||
std::string v;
|
||||
auto s = db_->Get(ReadOptions(), "k1", &v);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
|
||||
// But reports the issue with paranoid checks
|
||||
options_.paranoid_checks = true;
|
||||
Status s = TryReopen();
|
||||
s = TryReopen();
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(s.ToString().find("file size mismatch") != std::string::npos);
|
||||
|
||||
@@ -851,9 +922,6 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
|
||||
options.env = env_.get();
|
||||
options.paranoid_file_checks = true;
|
||||
options.create_if_missing = true;
|
||||
// Skip verifying record count against TableProperties for
|
||||
// MockTables
|
||||
options.compaction_verify_record_count = false;
|
||||
Status s;
|
||||
for (const auto& mode : corruption_modes) {
|
||||
delete db_;
|
||||
|
||||
+7
-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 {
|
||||
@@ -5086,6 +5062,8 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
|
||||
options_allow.create_if_missing = true;
|
||||
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
|
||||
@@ -5125,6 +5103,11 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
|
||||
EXPECT_EQ(Get(2, "b2"), "2");
|
||||
EXPECT_EQ(Get(3, "b3"), "NOT_FOUND");
|
||||
|
||||
std::unique_ptr<Iterator> iter(
|
||||
dbfull()->NewIterator(ReadOptions(), handles_[3]));
|
||||
iter->Seek("a3");
|
||||
ASSERT_OK(iter->status());
|
||||
iter.reset();
|
||||
// When the DB is re-opened with WAL entries for a CF that is newly setting
|
||||
// disallow_memtable_writes, we detect that and fail the open gracefully.
|
||||
ASSERT_EQ(TryReopenWithColumnFamilies(
|
||||
|
||||
+72
-57
@@ -506,6 +506,8 @@ TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
|
||||
table_options.prepopulate_block_cache =
|
||||
BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
// Include a compression dictionary block
|
||||
options.compression_opts.max_dict_bytes = 123;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
@@ -537,6 +539,9 @@ TEST_P(DBBlockCacheTest1, WarmCacheWithBlocksDuringFlush) {
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT));
|
||||
}
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_MISS));
|
||||
|
||||
// Including compression dict
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_MISS));
|
||||
}
|
||||
|
||||
// Verify compaction not counted
|
||||
@@ -824,68 +829,78 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
const int kNumEntriesPerFile = 128;
|
||||
const int kNumBytesPerEntry = 1024;
|
||||
|
||||
// Try all the available libraries that support dictionary compression
|
||||
std::vector<CompressionType> compression_types;
|
||||
if (Zlib_Supported()) {
|
||||
compression_types.push_back(kZlibCompression);
|
||||
}
|
||||
if (LZ4_Supported()) {
|
||||
compression_types.push_back(kLZ4Compression);
|
||||
compression_types.push_back(kLZ4HCCompression);
|
||||
}
|
||||
if (ZSTD_Supported()) {
|
||||
compression_types.push_back(kZSTD);
|
||||
}
|
||||
std::vector<CompressionType> dict_compressions =
|
||||
GetSupportedDictCompressions();
|
||||
Random rnd(301);
|
||||
for (auto compression_type : compression_types) {
|
||||
Options options = CurrentOptions();
|
||||
options.bottommost_compression = compression_type;
|
||||
options.bottommost_compression_opts.max_dict_bytes = 4096;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.block_cache.reset(new MockCache());
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
// Format version before and after compression handling changes
|
||||
for (int format_version : {6, 7}) {
|
||||
// Test all supported compression types because (at least historically)
|
||||
// dictionary compression could be enabled and a dictionary block saved
|
||||
// but ignored by some compression types. Ensure we at least don't crash
|
||||
// or return corruption for those.
|
||||
for (auto compression_type : GetSupportedCompressions()) {
|
||||
// Extra handling checks only for types actually supporting dictionary
|
||||
// compression.
|
||||
bool dict_supported =
|
||||
std::count(dict_compressions.begin(), dict_compressions.end(),
|
||||
compression_type) > 0;
|
||||
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
Options options = CurrentOptions();
|
||||
options.bottommost_compression = compression_type;
|
||||
options.bottommost_compression_opts.max_dict_bytes = 4096;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.block_cache.reset(new MockCache());
|
||||
table_options.format_version = format_version;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
std::string value = rnd.RandomString(kNumBytesPerEntry);
|
||||
ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
|
||||
for (int i = 0; i < kNumFiles; ++i) {
|
||||
ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
std::string value = rnd.RandomString(kNumBytesPerEntry);
|
||||
ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
if (dict_supported) {
|
||||
// Compression dictionary blocks are preloaded.
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, kNumFiles /* expected_compression_dict_misses */,
|
||||
0 /* expected_compression_dict_hits */,
|
||||
kNumFiles /* expected_compression_dict_inserts */);
|
||||
}
|
||||
|
||||
// Seek to a key in a file. It should cause the SST's dictionary
|
||||
// meta-block to be read.
|
||||
RecordCacheCounters(options);
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
ReadOptions read_options;
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
|
||||
|
||||
if (dict_supported) {
|
||||
// Two block hits: index and dictionary since they are prefetched
|
||||
// One block missed/added: data block
|
||||
CheckCacheCounters(options, 1 /* expected_misses */,
|
||||
2 /* expected_hits */, 1 /* expected_inserts */,
|
||||
0 /* expected_failures */);
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, 0 /* expected_compression_dict_misses */,
|
||||
1 /* expected_compression_dict_hits */,
|
||||
0 /* expected_compression_dict_inserts */);
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
// Compression dictionary blocks are preloaded.
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, kNumFiles /* expected_compression_dict_misses */,
|
||||
0 /* expected_compression_dict_hits */,
|
||||
kNumFiles /* expected_compression_dict_inserts */);
|
||||
|
||||
// Seek to a key in a file. It should cause the SST's dictionary meta-block
|
||||
// to be read.
|
||||
RecordCacheCounters(options);
|
||||
RecordCacheCountersForCompressionDict(options);
|
||||
ReadOptions read_options;
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
|
||||
// Two block hits: index and dictionary since they are prefetched
|
||||
// One block missed/added: data block
|
||||
CheckCacheCounters(options, 1 /* expected_misses */, 2 /* expected_hits */,
|
||||
1 /* expected_inserts */, 0 /* expected_failures */);
|
||||
CheckCacheCountersForCompressionDict(
|
||||
options, 0 /* expected_compression_dict_misses */,
|
||||
1 /* expected_compression_dict_hits */,
|
||||
0 /* expected_compression_dict_inserts */);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+896
-240
File diff suppressed because it is too large.
Load diff
@@ -17,9 +17,10 @@ class DBEncryptionTest : public DBTestBase {
|
||||
public:
|
||||
DBEncryptionTest()
|
||||
: DBTestBase("db_encryption_test", /*env_do_fsync=*/true) {}
|
||||
Env* GetTargetEnv() {
|
||||
Env* GetNonEncryptedEnv() {
|
||||
if (encrypted_env_ != nullptr) {
|
||||
return (static_cast<EnvWrapper*>(encrypted_env_))->target();
|
||||
return (static_cast_with_check<CompositeEnvWrapper>(encrypted_env_))
|
||||
->env_target();
|
||||
} else {
|
||||
return env_;
|
||||
}
|
||||
@@ -38,7 +39,7 @@ TEST_F(DBEncryptionTest, CheckEncrypted) {
|
||||
auto status = env_->GetChildren(dbname_, &fileNames);
|
||||
ASSERT_OK(status);
|
||||
|
||||
Env* target = GetTargetEnv();
|
||||
Env* target = GetNonEncryptedEnv();
|
||||
int hits = 0;
|
||||
for (auto it = fileNames.begin(); it != fileNames.end(); ++it) {
|
||||
if (*it == "LOCK") {
|
||||
@@ -89,7 +90,7 @@ TEST_F(DBEncryptionTest, CheckEncrypted) {
|
||||
}
|
||||
|
||||
TEST_F(DBEncryptionTest, ReadEmptyFile) {
|
||||
auto defaultEnv = GetTargetEnv();
|
||||
auto defaultEnv = GetNonEncryptedEnv();
|
||||
|
||||
// create empty file for reading it back in later
|
||||
auto envOptions = EnvOptions(CurrentOptions());
|
||||
@@ -116,6 +117,40 @@ TEST_F(DBEncryptionTest, ReadEmptyFile) {
|
||||
ASSERT_TRUE(data.empty());
|
||||
}
|
||||
|
||||
TEST_F(DBEncryptionTest, NotSupportedGetFileSize) {
|
||||
// Validate envrypted env does not support GetFileSize.
|
||||
// The goal of the test is to validate the encrypted env/fs does not support
|
||||
// GetFileSize API on FSRandomAccessFile interface.
|
||||
// This test combined with the rest of the integration tests validate that
|
||||
// the new API GetFileSize on FSRandomAccessFile interface is not required to
|
||||
// be supported for database to work properly.
|
||||
// The GetFileSize API is used in ReadFooterFromFile() API to get the file
|
||||
// size. When GetFileSize API is not supported, the ReadFooterFromFile() API
|
||||
// will use FileSystem GetFileSize API as fallback. Refer to the
|
||||
// EncryptedRandomAccessFile class definition for more details.
|
||||
if (!encrypted_env_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto fs = encrypted_env_->GetFileSystem();
|
||||
|
||||
// create empty file for reading it back in later
|
||||
auto filePath = dbname_ + "/empty.empty";
|
||||
|
||||
// Create empty file
|
||||
CreateFile(fs.get(), filePath, "", false);
|
||||
|
||||
// Open it for reading footer
|
||||
std::unique_ptr<FSRandomAccessFile> randomAccessFile;
|
||||
auto status = fs->NewRandomAccessFile(filePath, FileOptions(),
|
||||
&randomAccessFile, nullptr);
|
||||
ASSERT_OK(status);
|
||||
|
||||
uint64_t fileSize;
|
||||
status = randomAccessFile->GetFileSize(&fileSize);
|
||||
ASSERT_TRUE(status.IsNotSupported());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
+10
-14
@@ -75,11 +75,9 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
|
||||
ret.emplace_back(CurrentFileName(""));
|
||||
ret.emplace_back(DescriptorFileName("", versions_->manifest_file_number()));
|
||||
// The OPTIONS file number is zero in read-write mode when OPTIONS file
|
||||
// writing failed and the DB was configured with
|
||||
// `fail_if_options_file_error == false`. In read-only mode the OPTIONS file
|
||||
// number is zero when no OPTIONS file exist at all. In those cases we do not
|
||||
// record any OPTIONS file in the live file list.
|
||||
// In read-only mode the OPTIONS file number is zero when no OPTIONS file
|
||||
// exist at all. In this cases we do not record any OPTIONS file in the live
|
||||
// file list.
|
||||
if (versions_->options_file_number() != 0) {
|
||||
ret.emplace_back(OptionsFileName("", versions_->options_file_number()));
|
||||
}
|
||||
@@ -185,14 +183,14 @@ Status DBImpl::GetSortedWalFilesImpl(VectorWalPtr& files, bool need_seqnos) {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::GetCurrentWalFile(std::unique_ptr<WalFile>* current_log_file) {
|
||||
Status DBImpl::GetCurrentWalFile(std::unique_ptr<WalFile>* current_wal_file) {
|
||||
uint64_t current_logfile_number;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
current_logfile_number = logfile_number_;
|
||||
current_logfile_number = cur_wal_number_;
|
||||
}
|
||||
|
||||
return wal_manager_.GetLiveWalFile(current_logfile_number, current_log_file);
|
||||
return wal_manager_.GetLiveWalFile(current_logfile_number, current_wal_file);
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFilesStorageInfo(
|
||||
@@ -332,7 +330,7 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
const uint64_t options_size = versions_->options_file_size_;
|
||||
const uint64_t min_log_num = MinLogNumberToKeep();
|
||||
// Ensure consistency with manifest for track_and_verify_wals_in_manifest
|
||||
const uint64_t max_log_num = logfile_number_;
|
||||
const uint64_t max_log_num = cur_wal_number_;
|
||||
|
||||
mutex_.Unlock();
|
||||
|
||||
@@ -369,11 +367,9 @@ Status DBImpl::GetLiveFilesStorageInfo(
|
||||
}
|
||||
}
|
||||
|
||||
// The OPTIONS file number is zero in read-write mode when OPTIONS file
|
||||
// writing failed and the DB was configured with
|
||||
// `fail_if_options_file_error == false`. In read-only mode the OPTIONS file
|
||||
// number is zero when no OPTIONS file exist at all. In those cases we do not
|
||||
// record any OPTIONS file in the live file list.
|
||||
// In read-only mode the OPTIONS file number is zero when no OPTIONS file
|
||||
// exist at all. In this cases we do not record any OPTIONS file in the live
|
||||
// file list.
|
||||
if (options_number != 0) {
|
||||
results.emplace_back();
|
||||
LiveFileStorageInfo& info = results.back();
|
||||
|
||||
@@ -3504,6 +3504,209 @@ TEST_F(DBFlushTest, DBStuckAfterAtomicFlushError) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, VerifyOutputRecordCount) {
|
||||
for (bool use_plain_table : {false, true}) {
|
||||
Options options = CurrentOptions();
|
||||
options.flush_verify_memtable_count = true;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
// Verify flush output record count verification in different table
|
||||
// formats
|
||||
if (use_plain_table) {
|
||||
options.table_factory.reset(NewPlainTableFactory());
|
||||
}
|
||||
|
||||
// Verify that flush output record count verification does not produce false
|
||||
// positives.
|
||||
ASSERT_OK(Merge("k0", "v1"));
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
ASSERT_OK(Put("k2", "v1"));
|
||||
ASSERT_OK(SingleDelete("k2"));
|
||||
ASSERT_OK(Delete("k2"));
|
||||
ASSERT_OK(Delete("k3"));
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), "k1", "k3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Verify that flush output record count verification catch corruption
|
||||
DestroyAndReopen(options);
|
||||
if (use_plain_table) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PlainTableBuilder::Add::skip",
|
||||
[&](void* skip) { *(bool*)skip = true; });
|
||||
|
||||
} else {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlockBasedTableBuilder::Add::skip",
|
||||
[&](void* skip) { *(bool*)skip = true; });
|
||||
}
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
const char* expect =
|
||||
"Number of keys in flush output SST files does not match";
|
||||
|
||||
// 1. During DB open flush
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
ASSERT_OK(Put("k2", "v1"));
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), expect));
|
||||
|
||||
// 2. During regular flush
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
ASSERT_OK(Put("k2", "v1"));
|
||||
s = Flush();
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
ASSERT_TRUE(std::strstr(s.getState(), expect));
|
||||
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
|
||||
|
||||
+216
-215
@@ -74,6 +74,7 @@
|
||||
#include "options/cf_options.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "options/options_parser.h"
|
||||
#include "util/udt_util.h"
|
||||
#ifdef ROCKSDB_JEMALLOC
|
||||
#include "port/jemalloc_helper.h"
|
||||
#endif
|
||||
@@ -169,7 +170,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
bool read_only)
|
||||
: dbname_(dbname),
|
||||
own_info_log_(options.info_log == nullptr),
|
||||
init_logger_creation_s_(),
|
||||
initial_db_options_(SanitizeOptions(dbname, options, read_only,
|
||||
&init_logger_creation_s_)),
|
||||
env_(initial_db_options_.env),
|
||||
@@ -185,7 +185,6 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
mutex_(stats_, immutable_db_options_.clock, DB_MUTEX_WAIT_MICROS,
|
||||
immutable_db_options_.use_adaptive_mutex),
|
||||
#endif // COERCE_CONTEXT_SWITCH
|
||||
default_cf_handle_(nullptr),
|
||||
error_handler_(this, immutable_db_options_, &mutex_),
|
||||
event_logger_(immutable_db_options_.info_log.get()),
|
||||
max_total_in_memory_state_(0),
|
||||
@@ -194,45 +193,15 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
file_options_, immutable_db_options_)),
|
||||
seq_per_batch_(seq_per_batch),
|
||||
batch_per_txn_(batch_per_txn),
|
||||
next_job_id_(1),
|
||||
shutting_down_(false),
|
||||
reject_new_background_jobs_(false),
|
||||
db_lock_(nullptr),
|
||||
manual_compaction_paused_(false),
|
||||
bg_cv_(&mutex_),
|
||||
logfile_number_(0),
|
||||
log_dir_synced_(false),
|
||||
log_empty_(true),
|
||||
persist_stats_cf_handle_(nullptr),
|
||||
log_sync_cv_(&log_write_mutex_),
|
||||
total_log_size_(0),
|
||||
is_snapshot_supported_(true),
|
||||
wal_sync_cv_(&wal_write_mutex_),
|
||||
write_buffer_manager_(immutable_db_options_.write_buffer_manager.get()),
|
||||
write_thread_(immutable_db_options_),
|
||||
nonmem_write_thread_(immutable_db_options_),
|
||||
write_controller_(mutable_db_options_.delayed_write_rate),
|
||||
last_batch_group_size_(0),
|
||||
unscheduled_flushes_(0),
|
||||
unscheduled_compactions_(0),
|
||||
bg_bottom_compaction_scheduled_(0),
|
||||
bg_compaction_scheduled_(0),
|
||||
num_running_compactions_(0),
|
||||
bg_flush_scheduled_(0),
|
||||
num_running_flushes_(0),
|
||||
bg_purge_scheduled_(0),
|
||||
disable_delete_obsolete_files_(0),
|
||||
pending_purge_obsolete_files_(0),
|
||||
delete_obsolete_files_last_run_(immutable_db_options_.clock->NowMicros()),
|
||||
has_unpersisted_data_(false),
|
||||
unable_to_release_oldest_log_(false),
|
||||
num_running_ingest_file_(0),
|
||||
wal_manager_(immutable_db_options_, file_options_, io_tracer_,
|
||||
seq_per_batch),
|
||||
bg_work_paused_(0),
|
||||
bg_compaction_paused_(0),
|
||||
refitting_level_(false),
|
||||
opened_successfully_(false),
|
||||
periodic_task_scheduler_(),
|
||||
two_write_queues_(options.two_write_queues),
|
||||
manual_wal_flush_(options.manual_wal_flush),
|
||||
// last_sequencee_ is always maintained by the main queue that also writes
|
||||
@@ -250,14 +219,11 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
// requires a custom gc for compaction, we use that to set use_custom_gc_
|
||||
// as well.
|
||||
use_custom_gc_(seq_per_batch),
|
||||
shutdown_initiated_(false),
|
||||
own_sfm_(options.sst_file_manager == nullptr),
|
||||
closed_(false),
|
||||
atomic_flush_install_cv_(&mutex_),
|
||||
blob_callback_(immutable_db_options_.sst_file_manager.get(), &mutex_,
|
||||
&error_handler_, &event_logger_,
|
||||
immutable_db_options_.listeners, dbname_),
|
||||
lock_wal_count_(0) {
|
||||
immutable_db_options_.listeners, dbname_) {
|
||||
// !batch_per_trx_ implies seq_per_batch_ because it is only unset for
|
||||
// WriteUnprepared, which should use seq_per_batch_.
|
||||
assert(batch_per_txn_ || seq_per_batch_);
|
||||
@@ -287,12 +253,15 @@ DBImpl::DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
periodic_task_functions_.emplace(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
[this]() { this->RecordSeqnoToTimeMapping(); });
|
||||
periodic_task_functions_.emplace(
|
||||
PeriodicTaskType::kTriggerCompaction,
|
||||
[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()));
|
||||
|
||||
@@ -636,8 +605,8 @@ Status DBImpl::CloseHelper() {
|
||||
mutex_.Lock();
|
||||
}
|
||||
{
|
||||
InstrumentedMutexLock lock(&log_write_mutex_);
|
||||
for (auto l : logs_to_free_) {
|
||||
InstrumentedMutexLock lock(&wal_write_mutex_);
|
||||
for (auto l : wals_to_free_) {
|
||||
delete l;
|
||||
}
|
||||
for (auto& log : logs_) {
|
||||
@@ -821,7 +790,8 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kDumpStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kDumpStats),
|
||||
mutable_db_options_.stats_dump_period_sec);
|
||||
mutable_db_options_.stats_dump_period_sec,
|
||||
/*run_immediately=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -830,7 +800,8 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kPersistStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kPersistStats),
|
||||
mutable_db_options_.stats_persist_period_sec);
|
||||
mutable_db_options_.stats_persist_period_sec,
|
||||
/*run_immediately=*/true);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -838,7 +809,15 @@ Status DBImpl::StartPeriodicTaskScheduler() {
|
||||
|
||||
Status s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kFlushInfoLog,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kFlushInfoLog));
|
||||
periodic_task_functions_.at(PeriodicTaskType::kFlushInfoLog),
|
||||
/*run_immediately=*/true);
|
||||
|
||||
if (s.ok()) {
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kTriggerCompaction,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kTriggerCompaction),
|
||||
/*run_immediately=*/false);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -889,7 +868,7 @@ Status DBImpl::RegisterRecordSeqnoTimeWorker() {
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kRecordSeqnoTime,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kRecordSeqnoTime),
|
||||
seqno_time_cadence);
|
||||
seqno_time_cadence, /*run_immediately=*/true);
|
||||
}
|
||||
|
||||
return s;
|
||||
@@ -1090,7 +1069,7 @@ void DBImpl::DumpStats() {
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (!cfd->initialized()) {
|
||||
if (!cfd->initialized() || cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1180,11 +1159,11 @@ Status DBImpl::TablesRangeTombstoneSummary(ColumnFamilyHandle* column_family,
|
||||
|
||||
void DBImpl::ScheduleBgLogWriterClose(JobContext* job_context) {
|
||||
mutex_.AssertHeld();
|
||||
if (!job_context->logs_to_free.empty()) {
|
||||
for (auto l : job_context->logs_to_free) {
|
||||
if (!job_context->wals_to_free.empty()) {
|
||||
for (auto l : job_context->wals_to_free) {
|
||||
AddToLogsToFreeQueue(l);
|
||||
}
|
||||
job_context->logs_to_free.clear();
|
||||
job_context->wals_to_free.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1266,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();
|
||||
@@ -1399,7 +1376,7 @@ Status DBImpl::SetDBOptions(
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kDumpStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kDumpStats),
|
||||
new_options.stats_dump_period_sec);
|
||||
new_options.stats_dump_period_sec, /*run_immediately=*/true);
|
||||
}
|
||||
if (new_options.max_total_wal_size !=
|
||||
mutable_db_options_.max_total_wal_size) {
|
||||
@@ -1414,7 +1391,7 @@ Status DBImpl::SetDBOptions(
|
||||
s = periodic_task_scheduler_.Register(
|
||||
PeriodicTaskType::kPersistStats,
|
||||
periodic_task_functions_.at(PeriodicTaskType::kPersistStats),
|
||||
new_options.stats_persist_period_sec);
|
||||
new_options.stats_persist_period_sec, /*run_immediately=*/true);
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
@@ -1435,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_);
|
||||
@@ -1443,7 +1420,7 @@ Status DBImpl::SetDBOptions(
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
if (wal_other_option_changed ||
|
||||
total_log_size_ > GetMaxTotalWalSize()) {
|
||||
wals_total_size_.LoadRelaxed() > GetMaxTotalWalSize()) {
|
||||
Status purge_wal_status = SwitchWAL(&write_context);
|
||||
if (!purge_wal_status.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
@@ -1470,14 +1447,9 @@ Status DBImpl::SetDBOptions(
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "SetDBOptions() succeeded");
|
||||
new_options.Dump(immutable_db_options_.info_log.get());
|
||||
if (!persist_options_status.ok()) {
|
||||
if (immutable_db_options_.fail_if_options_file_error) {
|
||||
s = Status::IOError(
|
||||
"SetDBOptions() succeeded, but unable to persist options",
|
||||
persist_options_status.ToString());
|
||||
}
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unable to persist options in SetDBOptions() -- %s",
|
||||
persist_options_status.ToString().c_str());
|
||||
s = Status::IOError(
|
||||
"SetDBOptions() succeeded, but unable to persist options",
|
||||
persist_options_status.ToString());
|
||||
}
|
||||
} else {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log, "SetDBOptions failed");
|
||||
@@ -1508,12 +1480,18 @@ 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;
|
||||
{
|
||||
// We need to lock log_write_mutex_ since logs_ might change concurrently
|
||||
InstrumentedMutexLock wl(&log_write_mutex_);
|
||||
// We need to lock wal_write_mutex_ since logs_ might change concurrently
|
||||
InstrumentedMutexLock wl(&wal_write_mutex_);
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
io_s = cur_log_writer->WriteBuffer(write_options);
|
||||
}
|
||||
@@ -1540,7 +1518,7 @@ Status DBImpl::FlushWAL(const WriteOptions& write_options, bool sync) {
|
||||
}
|
||||
|
||||
bool DBImpl::WALBufferIsEmpty() {
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
auto res = cur_log_writer->BufferIsEmpty();
|
||||
return res;
|
||||
@@ -1548,7 +1526,7 @@ bool DBImpl::WALBufferIsEmpty() {
|
||||
|
||||
Status DBImpl::GetOpenWalSizes(std::map<uint64_t, uint64_t>& number_to_size) {
|
||||
assert(number_to_size.empty());
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
for (auto& log : logs_) {
|
||||
auto* open_file = log.writer->file();
|
||||
if (open_file) {
|
||||
@@ -1590,15 +1568,15 @@ IOStatus DBImpl::SyncWalImpl(bool include_current_wal,
|
||||
uint64_t up_to_number;
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
assert(!logs_.empty());
|
||||
|
||||
maybe_active_number = logfile_number_;
|
||||
maybe_active_number = cur_wal_number_;
|
||||
up_to_number =
|
||||
include_current_wal ? maybe_active_number : maybe_active_number - 1;
|
||||
|
||||
while (logs_.front().number <= up_to_number && logs_.front().IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
wal_sync_cv_.Wait();
|
||||
}
|
||||
// First check that logs are safe to sync in background.
|
||||
if (include_current_wal &&
|
||||
@@ -1622,7 +1600,7 @@ IOStatus DBImpl::SyncWalImpl(bool include_current_wal,
|
||||
}
|
||||
}
|
||||
|
||||
need_wal_dir_sync = !log_dir_synced_;
|
||||
need_wal_dir_sync = !wal_dir_synced_;
|
||||
}
|
||||
|
||||
if (include_current_wal) {
|
||||
@@ -1695,7 +1673,7 @@ IOStatus DBImpl::SyncWalImpl(bool include_current_wal,
|
||||
/*arg=*/nullptr);
|
||||
}
|
||||
{
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
for (auto* wal : wals_internally_closed) {
|
||||
// We can only modify the state of log::Writer under the mutex
|
||||
bool was_closed = wal->PublishIfClosed();
|
||||
@@ -1812,9 +1790,9 @@ Status DBImpl::UnlockWAL() {
|
||||
|
||||
void DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir,
|
||||
VersionEdit* synced_wals) {
|
||||
log_write_mutex_.AssertHeld();
|
||||
if (synced_dir && logfile_number_ == up_to) {
|
||||
log_dir_synced_ = true;
|
||||
wal_write_mutex_.AssertHeld();
|
||||
if (synced_dir && cur_wal_number_ == up_to) {
|
||||
wal_dir_synced_ = true;
|
||||
}
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;) {
|
||||
auto& wal = *it;
|
||||
@@ -1836,7 +1814,7 @@ void DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir,
|
||||
(immutable_db_options_.background_close_inactive_wals &&
|
||||
wal.GetPreSyncSize() == wal.writer->file()->GetFlushedSize())) {
|
||||
// Fully synced
|
||||
logs_to_free_.push_back(wal.ReleaseWriter());
|
||||
wals_to_free_.push_back(wal.ReleaseWriter());
|
||||
it = logs_.erase(it);
|
||||
} else {
|
||||
wal.FinishSync();
|
||||
@@ -1849,17 +1827,17 @@ void DBImpl::MarkLogsSynced(uint64_t up_to, bool synced_dir,
|
||||
++it;
|
||||
}
|
||||
}
|
||||
log_sync_cv_.SignalAll();
|
||||
wal_sync_cv_.SignalAll();
|
||||
}
|
||||
|
||||
void DBImpl::MarkLogsNotSynced(uint64_t up_to) {
|
||||
log_write_mutex_.AssertHeld();
|
||||
wal_write_mutex_.AssertHeld();
|
||||
for (auto it = logs_.begin(); it != logs_.end() && it->number <= up_to;
|
||||
++it) {
|
||||
auto& wal = *it;
|
||||
wal.FinishSync();
|
||||
}
|
||||
log_sync_cv_.SignalAll();
|
||||
wal_sync_cv_.SignalAll();
|
||||
}
|
||||
|
||||
SequenceNumber DBImpl::GetLatestSequenceNumber() const {
|
||||
@@ -1895,6 +1873,69 @@ Status DBImpl::GetFullHistoryTsLow(ColumnFamilyHandle* column_family,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::GetNewestUserDefinedTimestamp(ColumnFamilyHandle* column_family,
|
||||
std::string* newest_timestamp) {
|
||||
if (newest_timestamp == nullptr) {
|
||||
return Status::InvalidArgument("newest_timestamp is nullptr");
|
||||
}
|
||||
ColumnFamilyData* cfd = nullptr;
|
||||
if (column_family == nullptr) {
|
||||
cfd = default_cf_handle_->cfd();
|
||||
} else {
|
||||
auto cfh = static_cast_with_check<ColumnFamilyHandleImpl>(column_family);
|
||||
assert(cfh != nullptr);
|
||||
cfd = cfh->cfd();
|
||||
}
|
||||
assert(cfd != nullptr && cfd->user_comparator() != nullptr);
|
||||
if (cfd->user_comparator()->timestamp_size() == 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Timestamp is not enabled in this column family");
|
||||
}
|
||||
if (cfd->ioptions().persist_user_defined_timestamps) {
|
||||
return Status::NotSupported(
|
||||
"GetNewestUserDefinedTimestamp doesn't support the case when user"
|
||||
"defined timestamps are persisted.");
|
||||
}
|
||||
|
||||
Status status;
|
||||
// Acquire SuperVersion
|
||||
SuperVersion* sv = GetAndRefSuperVersion(cfd);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
bool enter_write_thread = sv->mem == cfd->mem();
|
||||
WriteThread::Writer w;
|
||||
// Enter write thread to read the mutable memtable to avoid racing access
|
||||
// with concurrent writes. No need to enter nonmem_write_thread_ since this
|
||||
// call only care about memtable writes, not WAL writes.
|
||||
if (enter_write_thread) {
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
WaitForPendingWrites();
|
||||
}
|
||||
*newest_timestamp = sv->mem->GetNewestUDT().ToString();
|
||||
assert(!newest_timestamp->empty() || sv->mem->IsEmpty());
|
||||
if (enter_write_thread) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
}
|
||||
}
|
||||
// Read from immutable memtables if nothing found in mutable memtable.
|
||||
if (newest_timestamp->empty()) {
|
||||
*newest_timestamp = sv->imm->GetNewestUDT().ToString();
|
||||
}
|
||||
// Read from SST files if no result can be found in memtables.
|
||||
if (newest_timestamp->empty() && sv->current->GetSstFilesSize() != 0) {
|
||||
// full_history_ts_low is used to track the exclusive upperbound of
|
||||
// flushed user defined timestamp. So we can use it to deduce the newest
|
||||
// timestamp in the SST files that the column family has seen.
|
||||
Slice full_history_ts_low = sv->full_history_ts_low;
|
||||
if (!full_history_ts_low.empty()) {
|
||||
GetU64CutoffTsFromFullHistoryTsLow(&full_history_ts_low,
|
||||
newest_timestamp);
|
||||
}
|
||||
}
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
return status;
|
||||
}
|
||||
|
||||
InternalIterator* DBImpl::NewInternalIterator(const ReadOptions& read_options,
|
||||
Arena* arena,
|
||||
SequenceNumber sequence,
|
||||
@@ -1928,10 +1969,10 @@ void DBImpl::BackgroundCallPurge() {
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallPurge:beforeMutexLock");
|
||||
mutex_.Lock();
|
||||
|
||||
while (!logs_to_free_queue_.empty()) {
|
||||
assert(!logs_to_free_queue_.empty());
|
||||
log::Writer* log_writer = *(logs_to_free_queue_.begin());
|
||||
logs_to_free_queue_.pop_front();
|
||||
while (!wals_to_free_queue_.empty()) {
|
||||
assert(!wals_to_free_queue_.empty());
|
||||
log::Writer* log_writer = *(wals_to_free_queue_.begin());
|
||||
wals_to_free_queue_.pop_front();
|
||||
mutex_.Unlock();
|
||||
delete log_writer;
|
||||
mutex_.Lock();
|
||||
@@ -3597,7 +3638,7 @@ Status DBImpl::CreateColumnFamilyImpl(const ReadOptions& read_options,
|
||||
edit.AddColumnFamily(column_family_name);
|
||||
uint32_t new_id = versions_->GetColumnFamilySet()->GetNextColumnFamilyID();
|
||||
edit.SetColumnFamily(new_id);
|
||||
edit.SetLogNumber(logfile_number_);
|
||||
edit.SetLogNumber(cur_wal_number_);
|
||||
edit.SetComparatorName(cf_options.comparator->Name());
|
||||
edit.SetPersistUserDefinedTimestamps(
|
||||
cf_options.persist_user_defined_timestamps);
|
||||
@@ -3796,6 +3837,14 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
return s.ok() || s.IsIncomplete();
|
||||
}
|
||||
|
||||
std::unique_ptr<MultiScan> DBImpl::NewMultiScan(
|
||||
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
|
||||
const MultiScanArgs& scan_opts) {
|
||||
std::unique_ptr<MultiScan> ms_iter = std::make_unique<MultiScan>(
|
||||
_read_options, scan_opts, this, column_family);
|
||||
return ms_iter;
|
||||
}
|
||||
|
||||
Iterator* DBImpl::NewIterator(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (_read_options.io_activity != Env::IOActivity::kUnknown &&
|
||||
@@ -3852,11 +3901,14 @@ Iterator* DBImpl::NewIterator(const ReadOptions& _read_options,
|
||||
|
||||
auto iter = new ForwardIterator(this, read_options, cfd, sv,
|
||||
/* allow_unprepared_value */ true);
|
||||
result = NewDBIterator(
|
||||
env_, read_options, cfd->ioptions(), sv->mutable_cf_options,
|
||||
cfd->user_comparator(), iter, sv->current, kMaxSequenceNumber,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */, cfh);
|
||||
// TODO(cbi): Add support for `memtable_op_scan_flush_trigger` for tailing
|
||||
// iterator. This requires refreshing DBIter's pointer to active_mem when
|
||||
// tailing iterator refreshes to new memtable internally.
|
||||
result = DBIter::NewIter(env_, read_options, cfd->ioptions(),
|
||||
sv->mutable_cf_options, cfd->user_comparator(),
|
||||
iter, sv->current, kMaxSequenceNumber,
|
||||
/*read_callback=*/nullptr, /*active_mem=*/nullptr,
|
||||
cfh, /*expose_blob_index=*/false);
|
||||
} else {
|
||||
// Note: no need to consider the special case of
|
||||
// last_seq_same_as_publish_seq_==false since NewIterator is overridden in
|
||||
@@ -3934,18 +3986,9 @@ ArenaWrappedDBIter* DBImpl::NewIteratorImpl(
|
||||
// Laying out the iterators in the order of being accessed makes it more
|
||||
// likely that any iterator pointer is close to the iterator it points to so
|
||||
// that they are likely to be in the same cache line and/or page.
|
||||
ArenaWrappedDBIter* db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, cfh->cfd()->ioptions(), sv->mutable_cf_options,
|
||||
sv->current, snapshot,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number, read_callback, cfh, expose_blob_index, allow_refresh);
|
||||
|
||||
InternalIterator* internal_iter = NewInternalIterator(
|
||||
db_iter->GetReadOptions(), cfh->cfd(), sv, db_iter->GetArena(), snapshot,
|
||||
/* allow_unprepared_value */ true, db_iter);
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
|
||||
return db_iter;
|
||||
return NewArenaWrappedDbIterator(
|
||||
env_, read_options, cfh, sv, snapshot, read_callback, this,
|
||||
expose_blob_index, allow_refresh, /*allow_mark_memtable_for_flush=*/true);
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> DBImpl::NewCoalescingIterator(
|
||||
@@ -4069,14 +4112,12 @@ Status DBImpl::NewIterators(
|
||||
auto iter = new ForwardIterator(this, read_options, cf_sv_pair.cfd,
|
||||
cf_sv_pair.super_version,
|
||||
/* allow_unprepared_value */ true);
|
||||
iterators->push_back(
|
||||
NewDBIterator(env_, read_options, cf_sv_pair.cfd->ioptions(),
|
||||
cf_sv_pair.super_version->mutable_cf_options,
|
||||
cf_sv_pair.cfd->user_comparator(), iter,
|
||||
cf_sv_pair.super_version->current, kMaxSequenceNumber,
|
||||
cf_sv_pair.super_version->mutable_cf_options
|
||||
.max_sequential_skip_in_iterations,
|
||||
nullptr /*read_callback*/, cf_sv_pair.cfh));
|
||||
iterators->push_back(DBIter::NewIter(
|
||||
env_, read_options, cf_sv_pair.cfd->ioptions(),
|
||||
cf_sv_pair.super_version->mutable_cf_options,
|
||||
cf_sv_pair.cfd->user_comparator(), iter,
|
||||
cf_sv_pair.super_version->current, kMaxSequenceNumber,
|
||||
nullptr /*read_callback*/, /*active_mem=*/nullptr, cf_sv_pair.cfh));
|
||||
}
|
||||
} else {
|
||||
for (const auto& cf_sv_pair : cf_sv_pairs) {
|
||||
@@ -4308,7 +4349,7 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
CfdList cf_scheduled;
|
||||
if (oldest_snapshot > bottommost_files_mark_threshold_) {
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!cfd->ioptions().allow_ingest_behind) {
|
||||
if (!cfd->AllowIngestBehind()) {
|
||||
cfd->current()->storage_info()->UpdateOldestSnapshot(
|
||||
oldest_snapshot, /*allow_ingest_behind=*/false);
|
||||
if (!cfd->current()
|
||||
@@ -4328,8 +4369,7 @@ void DBImpl::ReleaseSnapshot(const Snapshot* s) {
|
||||
// inaccurate.
|
||||
SequenceNumber new_bottommost_files_mark_threshold = kMaxSequenceNumber;
|
||||
for (auto* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (CfdListContains(cf_scheduled, cfd) ||
|
||||
cfd->ioptions().allow_ingest_behind) {
|
||||
if (CfdListContains(cf_scheduled, cfd) || cfd->AllowIngestBehind()) {
|
||||
continue;
|
||||
}
|
||||
new_bottommost_files_mark_threshold = std::min(
|
||||
@@ -4992,7 +5032,6 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
assert(column_family);
|
||||
auto* cfd =
|
||||
static_cast_with_check<ColumnFamilyHandleImpl>(column_family)->cfd();
|
||||
auto* sv = GetAndRefSuperVersion(cfd);
|
||||
{
|
||||
// Without mutex, Version::GetColumnFamilyMetaData will have data race
|
||||
// with Compaction::MarkFilesBeingCompacted. One solution is to use mutex,
|
||||
@@ -5004,9 +5043,21 @@ void DBImpl::GetColumnFamilyMetaData(ColumnFamilyHandle* column_family,
|
||||
// DB::GetColumnFamilyMetaData is not called frequently, the regression
|
||||
// should not be big. We still need to keep an eye on it.
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
sv->current->GetColumnFamilyMetaData(cf_meta);
|
||||
cfd->current()->GetColumnFamilyMetaData(cf_meta);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
ReturnAndCleanupSuperVersion(cfd, sv);
|
||||
}
|
||||
|
||||
void DBImpl::GetAllColumnFamilyMetaData(
|
||||
@@ -5020,85 +5071,6 @@ void DBImpl::GetAllColumnFamilyMetaData(
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::CheckConsistency() {
|
||||
mutex_.AssertHeld();
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
versions_->GetLiveFilesMetaData(&metadata);
|
||||
TEST_SYNC_POINT("DBImpl::CheckConsistency:AfterGetLiveFilesMetaData");
|
||||
|
||||
std::string corruption_messages;
|
||||
|
||||
if (immutable_db_options_.skip_checking_sst_file_sizes_on_db_open) {
|
||||
// Instead of calling GetFileSize() for each expected file, call
|
||||
// GetChildren() for the DB directory and check that all expected files
|
||||
// are listed, without checking their sizes.
|
||||
// Since sst files might be in different directories, do it for each
|
||||
// directory separately.
|
||||
std::map<std::string, std::vector<std::string>> files_by_directory;
|
||||
for (const auto& md : metadata) {
|
||||
// md.name has a leading "/". Remove it.
|
||||
std::string fname = md.name;
|
||||
if (!fname.empty() && fname[0] == '/') {
|
||||
fname = fname.substr(1);
|
||||
}
|
||||
files_by_directory[md.db_path].push_back(fname);
|
||||
}
|
||||
|
||||
IOOptions io_opts;
|
||||
io_opts.do_not_recurse = true;
|
||||
for (const auto& dir_files : files_by_directory) {
|
||||
std::string directory = dir_files.first;
|
||||
std::vector<std::string> existing_files;
|
||||
Status s = fs_->GetChildren(directory, io_opts, &existing_files,
|
||||
/*IODebugContext*=*/nullptr);
|
||||
if (!s.ok()) {
|
||||
corruption_messages +=
|
||||
"Can't list files in " + directory + ": " + s.ToString() + "\n";
|
||||
continue;
|
||||
}
|
||||
std::sort(existing_files.begin(), existing_files.end());
|
||||
|
||||
for (const std::string& fname : dir_files.second) {
|
||||
if (!std::binary_search(existing_files.begin(), existing_files.end(),
|
||||
fname) &&
|
||||
!std::binary_search(existing_files.begin(), existing_files.end(),
|
||||
Rocks2LevelTableFileName(fname))) {
|
||||
corruption_messages +=
|
||||
"Missing sst file " + fname + " in " + directory + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto& md : metadata) {
|
||||
// md.name has a leading "/".
|
||||
std::string file_path = md.db_path + md.name;
|
||||
|
||||
uint64_t fsize = 0;
|
||||
TEST_SYNC_POINT("DBImpl::CheckConsistency:BeforeGetFileSize");
|
||||
Status s = env_->GetFileSize(file_path, &fsize);
|
||||
if (!s.ok() &&
|
||||
env_->GetFileSize(Rocks2LevelTableFileName(file_path), &fsize).ok()) {
|
||||
s = Status::OK();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
corruption_messages +=
|
||||
"Can't access " + md.name + ": " + s.ToString() + "\n";
|
||||
} else if (fsize != md.size) {
|
||||
corruption_messages += "Sst file size mismatch: " + file_path +
|
||||
". Size recorded in manifest " +
|
||||
std::to_string(md.size) + ", actual size " +
|
||||
std::to_string(fsize) + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (corruption_messages.size() == 0) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
return Status::Corruption(corruption_messages);
|
||||
}
|
||||
}
|
||||
|
||||
Status DBImpl::GetDbIdentity(std::string& identity) const {
|
||||
identity.assign(db_id_);
|
||||
return Status::OK();
|
||||
@@ -5435,12 +5407,7 @@ Status DBImpl::WriteOptionsFile(const WriteOptions& write_options,
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"Unnable to persist options -- %s", s.ToString().c_str());
|
||||
if (immutable_db_options_.fail_if_options_file_error) {
|
||||
s = Status::IOError("Unable to persist options.", s.ToString().c_str());
|
||||
} else {
|
||||
// Ignore error
|
||||
s = Status::OK();
|
||||
}
|
||||
s = Status::IOError("Unable to persist options.", s.ToString().c_str());
|
||||
}
|
||||
|
||||
// Restore lock if appropriate
|
||||
@@ -5810,10 +5777,6 @@ Status DBImpl::IngestExternalFiles(
|
||||
for (const auto& arg : args) {
|
||||
const IngestExternalFileOptions& ingest_opts = arg.options;
|
||||
if (ingest_opts.ingest_behind) {
|
||||
if (!immutable_db_options_.allow_ingest_behind) {
|
||||
return Status::InvalidArgument(
|
||||
"can't ingest_behind file in DB with allow_ingest_behind=false");
|
||||
}
|
||||
auto ucmp = arg.column_family->GetComparator();
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0) {
|
||||
@@ -5821,6 +5784,14 @@ Status DBImpl::IngestExternalFiles(
|
||||
"Column family with user-defined "
|
||||
"timestamps enabled doesn't support ingest behind.");
|
||||
}
|
||||
|
||||
if (!static_cast<ColumnFamilyHandleImpl*>(arg.column_family)
|
||||
->cfd()
|
||||
->AllowIngestBehind()) {
|
||||
return Status::InvalidArgument(
|
||||
"Can't ingest_behind file in ColumnFamily %s with "
|
||||
"cf_allow_ingest_behind=false");
|
||||
}
|
||||
}
|
||||
if (arg.atomic_replace_range.has_value()) {
|
||||
if (ingest_opts.ingest_behind) {
|
||||
@@ -6055,18 +6026,19 @@ Status DBImpl::IngestExternalFiles(
|
||||
// mutex when persisting MANIFEST file, and the snapshots taken during
|
||||
// that period will not be stable if VersionSet last seqno is updated
|
||||
// before LogAndApply.
|
||||
int consumed_seqno_count =
|
||||
ingestion_jobs[0].ConsumedSequenceNumbersCount();
|
||||
SequenceNumber max_assigned_seqno =
|
||||
ingestion_jobs[0].MaxAssignedSequenceNumber();
|
||||
for (size_t i = 1; i != num_cfs; ++i) {
|
||||
consumed_seqno_count =
|
||||
std::max(consumed_seqno_count,
|
||||
ingestion_jobs[i].ConsumedSequenceNumbersCount());
|
||||
max_assigned_seqno = std::max(
|
||||
max_assigned_seqno, ingestion_jobs[i].MaxAssignedSequenceNumber());
|
||||
}
|
||||
if (consumed_seqno_count > 0) {
|
||||
if (max_assigned_seqno > 0) {
|
||||
const SequenceNumber last_seqno = versions_->LastSequence();
|
||||
versions_->SetLastAllocatedSequence(last_seqno + consumed_seqno_count);
|
||||
versions_->SetLastPublishedSequence(last_seqno + consumed_seqno_count);
|
||||
versions_->SetLastSequence(last_seqno + consumed_seqno_count);
|
||||
if (max_assigned_seqno > last_seqno) {
|
||||
versions_->SetLastAllocatedSequence(max_assigned_seqno);
|
||||
versions_->SetLastPublishedSequence(max_assigned_seqno);
|
||||
versions_->SetLastSequence(max_assigned_seqno);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6865,6 +6837,35 @@ void DBImpl::RecordSeqnoToTimeMapping() {
|
||||
sv_context.Clean();
|
||||
}
|
||||
|
||||
void DBImpl::TriggerPeriodicCompaction() {
|
||||
TEST_SYNC_POINT("DBImpl::TriggerPeriodicCompaction:StartRunning");
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Running the periodic task to trigger compactions.");
|
||||
|
||||
for (ColumnFamilyData* cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
if (cfd->GetLatestCFOptions().periodic_compaction_seconds &&
|
||||
!cfd->queued_for_compaction()) {
|
||||
cfd->current()->storage_info()->ComputeCompactionScore(
|
||||
cfd->ioptions(), cfd->GetLatestMutableCFOptions());
|
||||
EnqueuePendingCompaction(cfd);
|
||||
if (cfd->queued_for_compaction()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Periodic task to trigger compaction queued Column "
|
||||
"family [%s] for compaction.",
|
||||
cfd->GetName().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::TrackOrUntrackFiles(
|
||||
const std::vector<std::string>& existing_data_files, bool track) {
|
||||
auto sfm = static_cast_with_check<SstFileManagerImpl>(
|
||||
|
||||
+221
-152
@@ -173,10 +173,10 @@ struct DBOpenLogRecordReadReporter : public log::Reader::Reporter {
|
||||
|
||||
void OldLogRecord(size_t bytes) override;
|
||||
|
||||
uint64_t GetCorruptedLogNumber() const { return corrupted_log_number_; }
|
||||
uint64_t GetCorruptedLogNumber() const { return corrupted_wal_number_; }
|
||||
|
||||
private:
|
||||
uint64_t corrupted_log_number_ = kMaxSequenceNumber;
|
||||
uint64_t corrupted_wal_number_ = kMaxSequenceNumber;
|
||||
};
|
||||
|
||||
// While DB is the public interface of RocksDB, and DBImpl is the actual
|
||||
@@ -256,6 +256,10 @@ class DBImpl : public DB {
|
||||
Status WriteWithCallback(const WriteOptions& options, WriteBatch* updates,
|
||||
UserWriteCallback* user_write_cb) override;
|
||||
|
||||
Status IngestWriteBatchWithIndex(
|
||||
const WriteOptions& options,
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi) override;
|
||||
|
||||
using DB::Get;
|
||||
Status Get(const ReadOptions& _read_options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
@@ -379,6 +383,11 @@ class DBImpl : public DB {
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
using DB::NewMultiScan;
|
||||
std::unique_ptr<MultiScan> NewMultiScan(
|
||||
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
|
||||
const MultiScanArgs& scan_opts) override;
|
||||
|
||||
const Snapshot* GetSnapshot() override;
|
||||
void ReleaseSnapshot(const Snapshot* snapshot) override;
|
||||
|
||||
@@ -475,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;
|
||||
@@ -497,6 +509,9 @@ class DBImpl : public DB {
|
||||
Status GetFullHistoryTsLow(ColumnFamilyHandle* column_family,
|
||||
std::string* ts_low) override;
|
||||
|
||||
Status GetNewestUserDefinedTimestamp(ColumnFamilyHandle* column_family,
|
||||
std::string* newest_timestamp) override;
|
||||
|
||||
Status GetDbIdentity(std::string& identity) const override;
|
||||
|
||||
virtual Status GetDbIdentityFromIdentityFile(const IOOptions& opts,
|
||||
@@ -530,11 +545,11 @@ class DBImpl : public DB {
|
||||
|
||||
// Get the known flushed sizes of WALs that might still be written to
|
||||
// or have pending sync.
|
||||
// NOTE: unlike alive_log_files_, this function includes WALs that might
|
||||
// NOTE: unlike alive_wal_files_, this function includes WALs that might
|
||||
// be obsolete (but not obsolete to a pending Checkpoint) and not yet fully
|
||||
// synced.
|
||||
Status GetOpenWalSizes(std::map<uint64_t, uint64_t>& number_to_size);
|
||||
Status GetCurrentWalFile(std::unique_ptr<WalFile>* current_log_file) override;
|
||||
Status GetCurrentWalFile(std::unique_ptr<WalFile>* current_wal_file) override;
|
||||
Status GetCreationTimeOfOldestFile(uint64_t* creation_time) override;
|
||||
|
||||
Status GetUpdatesSince(
|
||||
@@ -558,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;
|
||||
|
||||
@@ -792,10 +812,6 @@ class DBImpl : public DB {
|
||||
// being detected.
|
||||
const Snapshot* GetSnapshotForWriteConflictBoundary();
|
||||
|
||||
// checks if all live files exist on file system and that their file sizes
|
||||
// match to our in-memory records
|
||||
virtual Status CheckConsistency();
|
||||
|
||||
// max_file_num_to_ignore allows bottom level compaction to filter out newly
|
||||
// compacted SST files. Setting max_file_num_to_ignore to kMaxUint64 will
|
||||
// disable the filtering
|
||||
@@ -1068,7 +1084,7 @@ class DBImpl : public DB {
|
||||
|
||||
void AddToLogsToFreeQueue(log::Writer* log_writer) {
|
||||
mutex_.AssertHeld();
|
||||
logs_to_free_queue_.push_back(log_writer);
|
||||
wals_to_free_queue_.push_back(log_writer);
|
||||
}
|
||||
|
||||
void AddSuperVersionsToFreeQueue(SuperVersion* sv) {
|
||||
@@ -1078,10 +1094,7 @@ class DBImpl : public DB {
|
||||
void SetSnapshotChecker(SnapshotChecker* snapshot_checker);
|
||||
|
||||
// Fill JobContext with snapshot information needed by flush and compaction.
|
||||
void GetSnapshotContext(JobContext* job_context,
|
||||
std::vector<SequenceNumber>* snapshot_seqs,
|
||||
SequenceNumber* earliest_write_conflict_snapshot,
|
||||
SnapshotChecker** snapshot_checker);
|
||||
void InitSnapshotContext(JobContext* job_context);
|
||||
|
||||
// Not thread-safe.
|
||||
void SetRecoverableStatePreReleaseCallback(PreReleaseCallback* callback);
|
||||
@@ -1133,7 +1146,7 @@ class DBImpl : public DB {
|
||||
bool TEST_UnableToReleaseOldestLog() { return unable_to_release_oldest_log_; }
|
||||
|
||||
bool TEST_IsLogGettingFlushed() {
|
||||
return alive_log_files_.begin()->getting_flushed;
|
||||
return alive_wal_files_.begin()->getting_flushed;
|
||||
}
|
||||
|
||||
Status TEST_SwitchMemtable(ColumnFamilyData* cfd = nullptr);
|
||||
@@ -1213,7 +1226,9 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t TEST_LogfileNumber();
|
||||
|
||||
uint64_t TEST_total_log_size() const { return total_log_size_; }
|
||||
uint64_t TEST_wals_total_size() const {
|
||||
return wals_total_size_.LoadRelaxed();
|
||||
}
|
||||
|
||||
void TEST_GetAllBlockCaches(std::unordered_set<const Cache*>* cache_set);
|
||||
|
||||
@@ -1275,6 +1290,12 @@ class DBImpl : public DB {
|
||||
// For the background timer job
|
||||
void RecordSeqnoToTimeMapping();
|
||||
|
||||
// Compactions rely on an event triggers like flush/compaction/SetOptions.
|
||||
// We need to trigger periodic compactions even when there is no such trigger.
|
||||
// This function checks and schedules available compactions and will run
|
||||
// periodically.
|
||||
void TriggerPeriodicCompaction();
|
||||
|
||||
// REQUIRES: DB mutex held
|
||||
std::pair<SequenceNumber, uint64_t> GetSeqnoToTimeSample() const;
|
||||
|
||||
@@ -1371,16 +1392,19 @@ class DBImpl : public DB {
|
||||
|
||||
// State below is protected by mutex_
|
||||
// With two_write_queues enabled, some of the variables that accessed during
|
||||
// WriteToWAL need different synchronization: log_empty_, alive_log_files_,
|
||||
// logs_, logfile_number_. Refer to the definition of each variable below for
|
||||
// WriteToWAL need different synchronization: wal_empty_, alive_wal_files_,
|
||||
// logs_, cur_wal_number_. Refer to the definition of each variable below for
|
||||
// more description.
|
||||
//
|
||||
// Protects access to most ColumnFamilyData methods, see more in comment for
|
||||
// each method.
|
||||
//
|
||||
// `mutex_` can be a hot lock in some workloads, so it deserves dedicated
|
||||
// cachelines.
|
||||
mutable CacheAlignedInstrumentedMutex mutex_;
|
||||
|
||||
ColumnFamilyHandleImpl* default_cf_handle_;
|
||||
InternalStats* default_cf_internal_stats_;
|
||||
ColumnFamilyHandleImpl* default_cf_handle_ = nullptr;
|
||||
InternalStats* default_cf_internal_stats_ = nullptr;
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
@@ -1392,7 +1416,7 @@ class DBImpl : public DB {
|
||||
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
std::atomic<uint64_t> max_total_in_memory_state_;
|
||||
std::atomic<uint64_t> max_total_in_memory_state_ = 0;
|
||||
|
||||
// The options to access storage files
|
||||
const FileOptions file_options_;
|
||||
@@ -1419,14 +1443,14 @@ class DBImpl : public DB {
|
||||
|
||||
// Each flush or compaction gets its own job id. this counter makes sure
|
||||
// they're unique
|
||||
std::atomic<int> next_job_id_;
|
||||
std::atomic<int> next_job_id_ = 1;
|
||||
|
||||
std::atomic<bool> shutting_down_;
|
||||
std::atomic<bool> shutting_down_ = false;
|
||||
|
||||
// No new background jobs can be queued if true. This is used to prevent new
|
||||
// background jobs from being queued after WaitForCompact() completes waiting
|
||||
// all background jobs then attempts to close when close_db_ option is true.
|
||||
bool reject_new_background_jobs_;
|
||||
bool reject_new_background_jobs_ = false;
|
||||
|
||||
// RecoveryContext struct stores the context about version edits along
|
||||
// with corresponding column_family_data and column_family_options.
|
||||
@@ -1524,11 +1548,11 @@ class DBImpl : public DB {
|
||||
// ingests `wbwi` is done.
|
||||
// @param memtable_updated Whether the same write that ingests wbwi has
|
||||
// updated memtable. This is useful for determining whether to set bg
|
||||
// error when IngestWBWI fails.
|
||||
Status IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
const WBWIMemTable::SeqnoRange& assigned_seqno,
|
||||
uint64_t min_prep_log, SequenceNumber last_seqno,
|
||||
bool memtable_updated, bool ignore_missing_cf);
|
||||
// error when IngestWBWIAsMemtable fails.
|
||||
Status IngestWBWIAsMemtable(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
const WBWIMemTable::SeqnoRange& assigned_seqno,
|
||||
uint64_t min_prep_log, SequenceNumber last_seqno,
|
||||
bool memtable_updated, bool ignore_missing_cf);
|
||||
|
||||
// If disable_memtable is set the application logic must guarantee that the
|
||||
// batch will still be skipped from memtable during the recovery. An excption
|
||||
@@ -1558,18 +1582,17 @@ class DBImpl : public DB {
|
||||
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
UserWriteCallback* user_write_cb = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
uint64_t* wal_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false, uint64_t* seq_used = nullptr,
|
||||
size_t batch_cnt = 0,
|
||||
PreReleaseCallback* pre_release_callback = nullptr,
|
||||
PostMemTableCallback* post_memtable_callback = nullptr,
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi = nullptr,
|
||||
uint64_t min_prep_log = 0);
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi = nullptr);
|
||||
|
||||
Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
UserWriteCallback* user_write_cb = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
uint64_t* wal_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false,
|
||||
uint64_t* seq_used = nullptr);
|
||||
|
||||
@@ -1596,7 +1619,7 @@ class DBImpl : public DB {
|
||||
Status WriteImplWALOnly(
|
||||
WriteThread* write_thread, const WriteOptions& options,
|
||||
WriteBatch* updates, WriteCallback* callback,
|
||||
UserWriteCallback* user_write_cb, uint64_t* log_used,
|
||||
UserWriteCallback* user_write_cb, uint64_t* wal_used,
|
||||
const uint64_t log_ref, uint64_t* seq_used, const size_t sub_batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback, const AssignOrder assign_order,
|
||||
const PublishLastSeq publish_last_seq, const bool disable_memtable);
|
||||
@@ -1757,9 +1780,9 @@ class DBImpl : public DB {
|
||||
}
|
||||
};
|
||||
|
||||
struct LogFileNumberSize {
|
||||
explicit LogFileNumberSize(uint64_t _number) : number(_number) {}
|
||||
LogFileNumberSize() {}
|
||||
struct WalFileNumberSize {
|
||||
explicit WalFileNumberSize(uint64_t _number) : number(_number) {}
|
||||
WalFileNumberSize() {}
|
||||
void AddSize(uint64_t new_size) { size += new_size; }
|
||||
uint64_t number;
|
||||
uint64_t size = 0;
|
||||
@@ -1781,6 +1804,13 @@ class DBImpl : public DB {
|
||||
if (writer->file()) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
s = writer->WriteBuffer(WriteOptions());
|
||||
if (attempt_truncate_size < SIZE_MAX &&
|
||||
attempt_truncate_size < writer->file()->GetFileSize()) {
|
||||
Status s2 = writer->file()->writable_file()->Truncate(
|
||||
attempt_truncate_size, IOOptions{}, nullptr);
|
||||
// This is just a best effort attempt
|
||||
s2.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
delete writer;
|
||||
writer = nullptr;
|
||||
@@ -1813,6 +1843,11 @@ class DBImpl : public DB {
|
||||
getting_synced = false;
|
||||
}
|
||||
|
||||
void SetAttemptTruncateSize(uint64_t size) {
|
||||
assert(attempt_truncate_size == SIZE_MAX);
|
||||
attempt_truncate_size = size;
|
||||
}
|
||||
|
||||
uint64_t number;
|
||||
// Visual Studio doesn't support deque's member to be noncopyable because
|
||||
// of a std::unique_ptr as a member.
|
||||
@@ -1825,15 +1860,20 @@ class DBImpl : public DB {
|
||||
// to be persisted even if appends happen during sync so it can be used for
|
||||
// tracking the synced size in MANIFEST.
|
||||
uint64_t pre_sync_size = 0;
|
||||
// When < SIZE_MAX, attempt to truncate the WAL to this size on close,
|
||||
// because a bad entry was written to it beyond that point and it likely
|
||||
// won't be recoverable with the bad entry.
|
||||
uint64_t attempt_truncate_size = SIZE_MAX;
|
||||
};
|
||||
|
||||
struct LogContext {
|
||||
explicit LogContext(bool need_sync = false)
|
||||
: need_log_sync(need_sync), need_log_dir_sync(need_sync) {}
|
||||
bool need_log_sync = false;
|
||||
bool need_log_dir_sync = false;
|
||||
struct WalContext {
|
||||
explicit WalContext(bool need_sync = false)
|
||||
: need_wal_sync(need_sync), need_wal_dir_sync(need_sync) {}
|
||||
bool need_wal_sync = false;
|
||||
bool need_wal_dir_sync = false;
|
||||
log::Writer* writer = nullptr;
|
||||
LogFileNumberSize* log_file_number_size = nullptr;
|
||||
WalFileNumberSize* wal_file_number_size = nullptr;
|
||||
uint64_t prev_size = SIZE_MAX;
|
||||
};
|
||||
|
||||
// PurgeFileInfo is a structure to hold information of files to be deleted in
|
||||
@@ -1925,12 +1965,19 @@ class DBImpl : public DB {
|
||||
};
|
||||
struct PrepickedCompaction {
|
||||
// background compaction takes ownership of `compaction`.
|
||||
// TODO(hx235): consider using std::shared_ptr for easier ownership
|
||||
// management
|
||||
Compaction* compaction;
|
||||
// caller retains ownership of `manual_compaction_state` as it is reused
|
||||
// across background compactions.
|
||||
ManualCompactionState* manual_compaction_state; // nullptr if non-manual
|
||||
// task limiter token is requested during compaction picking.
|
||||
std::unique_ptr<TaskLimiterToken> task_token;
|
||||
// If true, `compaction` is picked temporarily to express compaction intent
|
||||
// and will be released before re-picking a real compaction based on the
|
||||
// updated LSM shape when thread associated with `compaction` is ready to
|
||||
// run
|
||||
bool need_repick;
|
||||
};
|
||||
|
||||
struct CompactionArg {
|
||||
@@ -2021,14 +2068,13 @@ class DBImpl : public DB {
|
||||
// Flush the in-memory write buffer to storage. Switches to a new
|
||||
// log-file/memtable and writes a new descriptor iff successful. Then
|
||||
// installs a new super version for the column family.
|
||||
Status FlushMemTableToOutputFile(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options,
|
||||
bool* madeProgress, JobContext* job_context, FlushReason flush_reason,
|
||||
SuperVersionContext* superversion_context,
|
||||
std::vector<SequenceNumber>& snapshot_seqs,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri);
|
||||
Status FlushMemTableToOutputFile(ColumnFamilyData* cfd,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
bool* madeProgress, JobContext* job_context,
|
||||
FlushReason flush_reason,
|
||||
SuperVersionContext* superversion_context,
|
||||
LogBuffer* log_buffer,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
// Flush the memtables of (multiple) column families to multiple files on
|
||||
// persistent storage.
|
||||
@@ -2041,10 +2087,10 @@ class DBImpl : public DB {
|
||||
JobContext* job_context, LogBuffer* log_buffer, Env::Priority thread_pri);
|
||||
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
// corrupted_log_found is set to true if we recover from a corrupted log file.
|
||||
// corrupted_wal_found is set to true if we recover from a corrupted log file.
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* next_sequence, bool read_only,
|
||||
bool is_retry, bool* corrupted_log_found,
|
||||
bool is_retry, bool* corrupted_wal_found,
|
||||
RecoveryContext* recovery_ctx);
|
||||
|
||||
void SetupLogFilesRecovery(
|
||||
@@ -2152,12 +2198,12 @@ class DBImpl : public DB {
|
||||
// log file to its actual size, thereby freeing preallocated space.
|
||||
// Return success even if truncate fails
|
||||
Status GetLogSizeAndMaybeTruncate(uint64_t wal_number, bool truncate,
|
||||
LogFileNumberSize* log);
|
||||
WalFileNumberSize* log);
|
||||
|
||||
// Restore alive_log_files_ and total_log_size_ after recovery.
|
||||
// Restore alive_wal_files_ and wals_total_size_ after recovery.
|
||||
// It needs to run only when there's no flush during recovery
|
||||
// (e.g. avoid_flush_during_recovery=true). May also trigger flush
|
||||
// in case total_log_size > max_total_wal_size.
|
||||
// in case wals_total_size > max_total_wal_size.
|
||||
Status RestoreAliveLogFiles(const std::vector<uint64_t>& log_numbers);
|
||||
|
||||
// num_bytes: for slowdown case, delay time is calculated based on
|
||||
@@ -2306,7 +2352,7 @@ class DBImpl : public DB {
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
Status PreprocessWrite(const WriteOptions& write_options,
|
||||
LogContext* log_context, WriteContext* write_context);
|
||||
WalContext* log_context, WriteContext* write_context);
|
||||
|
||||
// Merge write batches in the write group into merged_batch.
|
||||
// Returns OK if merge is successful.
|
||||
@@ -2317,20 +2363,21 @@ class DBImpl : public DB {
|
||||
|
||||
IOStatus WriteToWAL(const WriteBatch& merged_batch,
|
||||
const WriteOptions& write_options,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
log::Writer* log_writer, uint64_t* wal_used,
|
||||
uint64_t* log_size,
|
||||
LogFileNumberSize& log_file_number_size,
|
||||
WalFileNumberSize& wal_file_number_size,
|
||||
SequenceNumber sequence);
|
||||
|
||||
IOStatus WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence,
|
||||
LogFileNumberSize& log_file_number_size);
|
||||
IOStatus WriteGroupToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* wal_used,
|
||||
bool need_wal_sync, bool need_wal_dir_sync,
|
||||
SequenceNumber sequence,
|
||||
WalFileNumberSize& wal_file_number_size);
|
||||
|
||||
IOStatus ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used,
|
||||
SequenceNumber* last_sequence, size_t seq_inc);
|
||||
IOStatus ConcurrentWriteGroupToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* wal_used,
|
||||
SequenceNumber* last_sequence,
|
||||
size_t seq_inc);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ if paranoid check is enabled.
|
||||
// Caller must hold mutex_.
|
||||
@@ -2344,7 +2391,7 @@ class DBImpl : public DB {
|
||||
void WALIOStatusCheck(const IOStatus& status);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ in case of memtable insert error.
|
||||
void MemTableInsertStatusCheck(const Status& memtable_insert_status);
|
||||
void HandleMemTableInsertFailure(const Status& nonok_memtable_insert_status);
|
||||
|
||||
Status CompactFilesImpl(const CompactionOptions& compact_options,
|
||||
ColumnFamilyData* cfd, Version* version,
|
||||
@@ -2354,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);
|
||||
@@ -2429,6 +2484,8 @@ class DBImpl : public DB {
|
||||
bool* flush_rescheduled_to_retain_udt,
|
||||
Env::Priority thread_pri);
|
||||
|
||||
Compaction* CreateIntendedCompactionForwardedToBottomPriorityPool(
|
||||
Compaction* c);
|
||||
bool EnoughRoomForCompaction(ColumnFamilyData* cfd,
|
||||
const std::vector<CompactionInputFiles>& inputs,
|
||||
bool* sfm_bookkeeping, LogBuffer* log_buffer);
|
||||
@@ -2546,7 +2603,7 @@ class DBImpl : public DB {
|
||||
bool ShouldntRunManualCompaction(ManualCompactionState* m);
|
||||
bool HaveManualCompaction(ColumnFamilyData* cfd);
|
||||
bool MCOverlap(ManualCompactionState* m, ManualCompactionState* m1);
|
||||
void UpdateDeletionCompactionStats(const std::unique_ptr<Compaction>& c);
|
||||
void UpdateFIFOCompactionStatus(const std::unique_ptr<Compaction>& c);
|
||||
|
||||
// May open and read table files for table property.
|
||||
// Should not be called while holding mutex_.
|
||||
@@ -2696,8 +2753,13 @@ class DBImpl : public DB {
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
ErrorIteratorFuncType error_iterator_func);
|
||||
|
||||
bool ShouldPickCompaction(bool is_prepicked,
|
||||
const PrepickedCompaction* prepicked_compaction);
|
||||
|
||||
void ResetBottomPriCompactionIntent(ColumnFamilyData* cfd,
|
||||
std::unique_ptr<Compaction>& c);
|
||||
// Lock over the persistent DB state. Non-nullptr iff successfully acquired.
|
||||
FileLock* db_lock_;
|
||||
FileLock* db_lock_ = nullptr;
|
||||
|
||||
// Guards changes to DB and CF options to ensure consistency between
|
||||
// * In-memory options objects
|
||||
@@ -2711,20 +2773,20 @@ class DBImpl : public DB {
|
||||
// Guards reads and writes to in-memory stats_history_.
|
||||
InstrumentedMutex stats_history_mutex_;
|
||||
|
||||
// In addition to mutex_, log_write_mutex_ protects writes to logs_ and
|
||||
// logfile_number_. With two_write_queues it also protects alive_log_files_,
|
||||
// and log_empty_. Refer to the definition of each variable below for more
|
||||
// In addition to mutex_, wal_write_mutex_ protects writes to logs_ and
|
||||
// cur_wal_number_. With two_write_queues it also protects alive_wal_files_,
|
||||
// and wal_empty_. Refer to the definition of each variable below for more
|
||||
// details.
|
||||
// Note: to avoid deadlock, if needed to acquire both log_write_mutex_ and
|
||||
// mutex_, the order should be first mutex_ and then log_write_mutex_.
|
||||
InstrumentedMutex log_write_mutex_;
|
||||
// Note: to avoid deadlock, if needed to acquire both wal_write_mutex_ and
|
||||
// mutex_, the order should be first mutex_ and then wal_write_mutex_.
|
||||
InstrumentedMutex wal_write_mutex_;
|
||||
|
||||
// If zero, manual compactions are allowed to proceed. If non-zero, manual
|
||||
// compactions may still be running, but will quickly fail with
|
||||
// `Status::Incomplete`. The value indicates how many threads have paused
|
||||
// manual compactions. It is accessed in read mode outside the DB mutex in
|
||||
// compaction code paths.
|
||||
std::atomic<int> manual_compaction_paused_;
|
||||
std::atomic<int> manual_compaction_paused_ = false;
|
||||
|
||||
// This condition variable is signaled on these conditions:
|
||||
// * whenever bg_compaction_scheduled_ goes down to 0
|
||||
@@ -2740,106 +2802,114 @@ class DBImpl : public DB {
|
||||
// * whenever SetOptions successfully updates options.
|
||||
// * whenever a column family is dropped.
|
||||
InstrumentedCondVar bg_cv_;
|
||||
// Writes are protected by locking both mutex_ and log_write_mutex_, and reads
|
||||
// must be under either mutex_ or log_write_mutex_. Since after ::Open,
|
||||
// logfile_number_ is currently updated only in write_thread_, it can be read
|
||||
|
||||
ColumnFamilyHandleImpl* persist_stats_cf_handle_ = nullptr;
|
||||
|
||||
bool persistent_stats_cfd_exists_ = true;
|
||||
|
||||
// Writes are protected by locking both mutex_ and wal_write_mutex_, and reads
|
||||
// must be under either mutex_ or wal_write_mutex_. Since after ::Open,
|
||||
// cur_wal_number_ is currently updated only in write_thread_, it can be read
|
||||
// from the same write_thread_ without any locks.
|
||||
uint64_t logfile_number_;
|
||||
uint64_t cur_wal_number_ = 0;
|
||||
|
||||
// Log files that we can recycle. Must be protected by db mutex_.
|
||||
std::deque<uint64_t> log_recycle_files_;
|
||||
std::deque<uint64_t> wal_recycle_files_;
|
||||
|
||||
// The minimum log file number taht can be recycled, if log recycling is
|
||||
// enabled. This is used to ensure that log files created by previous
|
||||
// instances of the database are not recycled, as we cannot be sure they
|
||||
// were created in the recyclable format.
|
||||
uint64_t min_log_number_to_recycle_;
|
||||
// Protected by log_write_mutex_.
|
||||
bool log_dir_synced_;
|
||||
// Without two_write_queues, read and writes to log_empty_ are protected by
|
||||
uint64_t min_wal_number_to_recycle_ = 0;
|
||||
|
||||
// Protected by wal_write_mutex_.
|
||||
bool wal_dir_synced_ = false;
|
||||
|
||||
// Without two_write_queues, read and writes to wal_empty_ are protected by
|
||||
// mutex_. Since it is currently updated/read only in write_thread_, it can be
|
||||
// accessed from the same write_thread_ without any locks. With
|
||||
// two_write_queues writes, where it can be updated in different threads,
|
||||
// read and writes are protected by log_write_mutex_ instead. This is to avoid
|
||||
// expensive mutex_ lock during WAL write, which update log_empty_.
|
||||
bool log_empty_;
|
||||
|
||||
ColumnFamilyHandleImpl* persist_stats_cf_handle_;
|
||||
|
||||
bool persistent_stats_cfd_exists_ = true;
|
||||
// read and writes are protected by wal_write_mutex_ instead. This is to avoid
|
||||
// expensive mutex_ lock during WAL write, which update wal_empty_.
|
||||
bool wal_empty_ = true;
|
||||
|
||||
// The current WAL file and those that have not been found obsolete from
|
||||
// memtable flushes. A WAL not on this list might still be pending writer
|
||||
// flush and/or sync and close and might still be in logs_. alive_log_files_
|
||||
// is protected by mutex_ and log_write_mutex_ with details as follows:
|
||||
// flush and/or sync and close and might still be in logs_. alive_wal_files_
|
||||
// is protected by mutex_ and wal_write_mutex_ with details as follows:
|
||||
// 1. read by FindObsoleteFiles() which can be called in either application
|
||||
// thread or RocksDB bg threads, both mutex_ and log_write_mutex_ are
|
||||
// thread or RocksDB bg threads, both mutex_ and wal_write_mutex_ are
|
||||
// held.
|
||||
// 2. pop_front() by FindObsoleteFiles(), both mutex_ and log_write_mutex_
|
||||
// 2. pop_front() by FindObsoleteFiles(), both mutex_ and wal_write_mutex_
|
||||
// are held.
|
||||
// 3. push_back() by DBImpl::Open() and DBImpl::RestoreAliveLogFiles()
|
||||
// (actually called by Open()), only mutex_ is held because at this point,
|
||||
// the DB::Open() call has not returned success to application, and the
|
||||
// only other thread(s) that can conflict are bg threads calling
|
||||
// FindObsoleteFiles() which ensure that both mutex_ and log_write_mutex_
|
||||
// are held when accessing alive_log_files_.
|
||||
// FindObsoleteFiles() which ensure that both mutex_ and wal_write_mutex_
|
||||
// are held when accessing alive_wal_files_.
|
||||
// 4. read by DBImpl::Open() is protected by mutex_.
|
||||
// 5. push_back() by SwitchMemtable(). Both mutex_ and log_write_mutex_ are
|
||||
// 5. push_back() by SwitchMemtable(). Both mutex_ and wal_write_mutex_ are
|
||||
// held. This is done by the write group leader. Note that in the case of
|
||||
// two-write-queues, another WAL-only write thread can be writing to the
|
||||
// WAL concurrently. See 9.
|
||||
// 6. read by SwitchWAL() with both mutex_ and log_write_mutex_ held. This is
|
||||
// 6. read by SwitchWAL() with both mutex_ and wal_write_mutex_ held. This is
|
||||
// done by write group leader.
|
||||
// 7. read by ConcurrentWriteToWAL() by the write group leader in the case of
|
||||
// two-write-queues. Only log_write_mutex_ is held to protect concurrent
|
||||
// two-write-queues. Only wal_write_mutex_ is held to protect concurrent
|
||||
// pop_front() by FindObsoleteFiles().
|
||||
// 8. read by PreprocessWrite() by the write group leader. log_write_mutex_
|
||||
// 8. read by PreprocessWrite() by the write group leader. wal_write_mutex_
|
||||
// is held to protect the data structure from concurrent pop_front() by
|
||||
// FindObsoleteFiles().
|
||||
// 9. read by ConcurrentWriteToWAL() by a WAL-only write thread in the case
|
||||
// of two-write-queues. Only log_write_mutex_ is held. This suffices to
|
||||
// of two-write-queues. Only wal_write_mutex_ is held. This suffices to
|
||||
// protect the data structure from concurrent push_back() by current
|
||||
// write group leader as well as pop_front() by FindObsoleteFiles().
|
||||
std::deque<LogFileNumberSize> alive_log_files_;
|
||||
std::deque<WalFileNumberSize> alive_wal_files_;
|
||||
|
||||
// Total size of all "alive" WALs (for easy access without synchronization)
|
||||
RelaxedAtomic<uint64_t> wals_total_size_{0};
|
||||
|
||||
// Log files that aren't fully synced, and the current log file.
|
||||
// Synchronization:
|
||||
// 1. read by FindObsoleteFiles() which can be called either in application
|
||||
// thread or RocksDB bg threads. log_write_mutex_ is always held, while
|
||||
// thread or RocksDB bg threads. wal_write_mutex_ is always held, while
|
||||
// some reads are performed without mutex_.
|
||||
// 2. pop_front() by FindObsoleteFiles() with only log_write_mutex_ held.
|
||||
// 3. read by DBImpl::Open() with both mutex_ and log_write_mutex_.
|
||||
// 4. emplace_back() by DBImpl::Open() with both mutex_ and log_write_mutex.
|
||||
// 2. pop_front() by FindObsoleteFiles() with only wal_write_mutex_ held.
|
||||
// 3. read by DBImpl::Open() with both mutex_ and wal_write_mutex_.
|
||||
// 4. emplace_back() by DBImpl::Open() with both mutex_ and wal_write_mutex.
|
||||
// Note that at this point, DB::Open() has not returned success to
|
||||
// application, thus the only other thread(s) that can conflict are bg
|
||||
// threads calling FindObsoleteFiles(). See 1.
|
||||
// 5. iteration and clear() from CloseHelper() always hold log_write_mutex
|
||||
// 5. iteration and clear() from CloseHelper() always hold wal_write_mutex
|
||||
// and mutex_.
|
||||
// 6. back() called by APIs FlushWAL() and LockWAL() are protected by only
|
||||
// log_write_mutex_. These two can be called by application threads after
|
||||
// wal_write_mutex_. These two can be called by application threads after
|
||||
// DB::Open() returns success to applications.
|
||||
// 7. read by SyncWAL(), another API, protected by only log_write_mutex_.
|
||||
// 7. read by SyncWAL(), another API, protected by only wal_write_mutex_.
|
||||
// 8. read by MarkLogsNotSynced() and MarkLogsSynced() are protected by
|
||||
// log_write_mutex_.
|
||||
// 9. erase() by MarkLogsSynced() protected by log_write_mutex_.
|
||||
// 10. read by SyncClosedWals() protected by only log_write_mutex_. This can
|
||||
// wal_write_mutex_.
|
||||
// 9. erase() by MarkLogsSynced() protected by wal_write_mutex_.
|
||||
// 10. read by SyncClosedWals() protected by only wal_write_mutex_. This can
|
||||
// happen in bg flush threads after DB::Open() returns success to
|
||||
// applications.
|
||||
// 11. reads, e.g. front(), iteration, and back() called by PreprocessWrite()
|
||||
// holds only the log_write_mutex_. This is done by the write group
|
||||
// holds only the wal_write_mutex_. This is done by the write group
|
||||
// leader. A bg thread calling FindObsoleteFiles() or MarkLogsSynced()
|
||||
// can happen concurrently. This is fine because log_write_mutex_ is used
|
||||
// can happen concurrently. This is fine because wal_write_mutex_ is used
|
||||
// by all parties. See 2, 5, 9.
|
||||
// 12. reads, empty(), back() called by SwitchMemtable() hold both mutex_ and
|
||||
// log_write_mutex_. This happens in the write group leader.
|
||||
// wal_write_mutex_. This happens in the write group leader.
|
||||
// 13. emplace_back() by SwitchMemtable() hold both mutex_ and
|
||||
// log_write_mutex_. This happens in the write group leader. Can conflict
|
||||
// wal_write_mutex_. This happens in the write group leader. Can conflict
|
||||
// with bg threads calling FindObsoleteFiles(), MarkLogsSynced(),
|
||||
// SyncClosedWals(), etc. as well as application threads calling
|
||||
// FlushWAL(), SyncWAL(), LockWAL(). This is fine because all parties
|
||||
// require at least log_write_mutex_.
|
||||
// require at least wal_write_mutex_.
|
||||
// 14. iteration called in WriteToWAL(write_group) protected by
|
||||
// log_write_mutex_. This is done by write group leader when
|
||||
// wal_write_mutex_. This is done by write group leader when
|
||||
// two-write-queues is disabled and write needs to sync logs.
|
||||
// 15. back() called in ConcurrentWriteToWAL() protected by log_write_mutex_.
|
||||
// 15. back() called in ConcurrentWriteToWAL() protected by wal_write_mutex_.
|
||||
// This can be done by the write group leader if two-write-queues is
|
||||
// enabled. It can also be done by another WAL-only write thread.
|
||||
//
|
||||
@@ -2856,23 +2926,22 @@ class DBImpl : public DB {
|
||||
std::deque<LogWriterNumber> logs_;
|
||||
|
||||
// Signaled when getting_synced becomes false for some of the logs_.
|
||||
InstrumentedCondVar log_sync_cv_;
|
||||
InstrumentedCondVar wal_sync_cv_;
|
||||
// This is the app-level state that is written to the WAL but will be used
|
||||
// only during recovery. Using this feature enables not writing the state to
|
||||
// memtable on normal writes and hence improving the throughput. Each new
|
||||
// write of the state will replace the previous state entirely even if the
|
||||
// keys in the two consecutive states do not overlap.
|
||||
// It is protected by log_write_mutex_ when two_write_queues_ is enabled.
|
||||
// It is protected by wal_write_mutex_ when two_write_queues_ is enabled.
|
||||
// Otherwise only the heaad of write_thread_ can access it.
|
||||
WriteBatch cached_recoverable_state_;
|
||||
std::atomic<bool> cached_recoverable_state_empty_ = {true};
|
||||
std::atomic<uint64_t> total_log_size_;
|
||||
|
||||
// If this is non-empty, we need to delete these log files in background
|
||||
// threads. Protected by log_write_mutex_.
|
||||
autovector<log::Writer*> logs_to_free_;
|
||||
// threads. Protected by wal_write_mutex_.
|
||||
autovector<log::Writer*> wals_to_free_;
|
||||
|
||||
bool is_snapshot_supported_;
|
||||
bool is_snapshot_supported_ = true;
|
||||
|
||||
std::map<uint64_t, std::map<std::string, uint64_t>> stats_history_;
|
||||
|
||||
@@ -2896,7 +2965,7 @@ class DBImpl : public DB {
|
||||
// sleep if it uses up the quota.
|
||||
// Note: This is to protect memtable and compaction. If the batch only writes
|
||||
// to the WAL its size need not to be included in this.
|
||||
uint64_t last_batch_group_size_;
|
||||
uint64_t last_batch_group_size_ = 0;
|
||||
|
||||
FlushScheduler flush_scheduler_;
|
||||
|
||||
@@ -2955,32 +3024,32 @@ class DBImpl : public DB {
|
||||
std::unordered_set<uint64_t> files_grabbed_for_purge_;
|
||||
|
||||
// A queue to store log writers to close. Protected by db mutex_.
|
||||
std::deque<log::Writer*> logs_to_free_queue_;
|
||||
std::deque<log::Writer*> wals_to_free_queue_;
|
||||
|
||||
std::deque<SuperVersion*> superversions_to_free_queue_;
|
||||
|
||||
int unscheduled_flushes_;
|
||||
int unscheduled_flushes_ = 0;
|
||||
|
||||
int unscheduled_compactions_;
|
||||
int unscheduled_compactions_ = 0;
|
||||
|
||||
// count how many background compactions are running or have been scheduled in
|
||||
// the BOTTOM pool
|
||||
int bg_bottom_compaction_scheduled_;
|
||||
int bg_bottom_compaction_scheduled_ = 0;
|
||||
|
||||
// count how many background compactions are running or have been scheduled
|
||||
int bg_compaction_scheduled_;
|
||||
int bg_compaction_scheduled_ = 0;
|
||||
|
||||
// stores the number of compactions are currently running
|
||||
int num_running_compactions_;
|
||||
int num_running_compactions_ = 0;
|
||||
|
||||
// number of background memtable flush jobs, submitted to the HIGH pool
|
||||
int bg_flush_scheduled_;
|
||||
int bg_flush_scheduled_ = 0;
|
||||
|
||||
// stores the number of flushes are currently running
|
||||
int num_running_flushes_;
|
||||
int num_running_flushes_ = 0;
|
||||
|
||||
// number of background obsolete file purge jobs, submitted to the HIGH pool
|
||||
int bg_purge_scheduled_;
|
||||
int bg_purge_scheduled_ = 0;
|
||||
|
||||
std::deque<ManualCompactionState*> manual_compaction_dequeue_;
|
||||
|
||||
@@ -2990,11 +3059,11 @@ class DBImpl : public DB {
|
||||
// This enables two different threads to call
|
||||
// EnableFileDeletions() and DisableFileDeletions()
|
||||
// without any synchronization
|
||||
int disable_delete_obsolete_files_;
|
||||
int disable_delete_obsolete_files_ = 0;
|
||||
|
||||
// Number of times FindObsoleteFiles has found deletable files and the
|
||||
// corresponding call to PurgeObsoleteFiles has not yet finished.
|
||||
int pending_purge_obsolete_files_;
|
||||
int pending_purge_obsolete_files_ = 0;
|
||||
|
||||
// last time when DeleteObsoleteFiles with full scan was executed. Originally
|
||||
// initialized with startup time.
|
||||
@@ -3006,12 +3075,12 @@ class DBImpl : public DB {
|
||||
// The mutex used by switch_cv_. mutex_ should be acquired beforehand.
|
||||
std::mutex switch_mutex_;
|
||||
// Number of threads intending to write to memtable
|
||||
std::atomic<size_t> pending_memtable_writes_ = {};
|
||||
std::atomic<size_t> pending_memtable_writes_{0};
|
||||
|
||||
// A flag indicating whether the current rocksdb database has any
|
||||
// data that is not yet persisted into either WAL or SST file.
|
||||
// Used when disableWAL is true.
|
||||
std::atomic<bool> has_unpersisted_data_;
|
||||
std::atomic<bool> has_unpersisted_data_{false};
|
||||
|
||||
// if an attempt was made to flush all column families that
|
||||
// the oldest log depends on but uncommitted data in the oldest
|
||||
@@ -3019,26 +3088,26 @@ class DBImpl : public DB {
|
||||
// We must attempt to free the dependent memtables again
|
||||
// at a later time after the transaction in the oldest
|
||||
// log is fully commited.
|
||||
bool unable_to_release_oldest_log_;
|
||||
bool unable_to_release_oldest_log_{false};
|
||||
|
||||
// Number of running IngestExternalFile() or CreateColumnFamilyWithImport()
|
||||
// calls.
|
||||
// REQUIRES: mutex held
|
||||
int num_running_ingest_file_;
|
||||
int num_running_ingest_file_ = 0;
|
||||
|
||||
WalManager wal_manager_;
|
||||
|
||||
// A value of > 0 temporarily disables scheduling of background work
|
||||
int bg_work_paused_;
|
||||
int bg_work_paused_ = 0;
|
||||
|
||||
// A value of > 0 temporarily disables scheduling of background compaction
|
||||
int bg_compaction_paused_;
|
||||
int bg_compaction_paused_ = 0;
|
||||
|
||||
// Guard against multiple concurrent refitting
|
||||
bool refitting_level_;
|
||||
bool refitting_level_ = false;
|
||||
|
||||
// Indicate DB was opened successfully
|
||||
bool opened_successfully_;
|
||||
bool opened_successfully_ = false;
|
||||
|
||||
// The min threshold to triggere bottommost compaction for removing
|
||||
// garbages, among all column families.
|
||||
@@ -3084,13 +3153,13 @@ class DBImpl : public DB {
|
||||
// error recovery from going on in parallel. The latter, shutting_down_,
|
||||
// is set a little later during the shutdown after scheduling memtable
|
||||
// flushes
|
||||
std::atomic<bool> shutdown_initiated_;
|
||||
std::atomic<bool> shutdown_initiated_{false};
|
||||
// Flag to indicate whether sst_file_manager object was allocated in
|
||||
// DB::Open() or passed to us
|
||||
bool own_sfm_;
|
||||
|
||||
// Flag to check whether Close() has been called on this DB
|
||||
bool closed_;
|
||||
bool closed_ = false;
|
||||
// save the closing status, for re-calling the close()
|
||||
Status closing_status_;
|
||||
// mutex for DB::Close()
|
||||
@@ -3126,7 +3195,7 @@ class DBImpl : public DB {
|
||||
|
||||
// The number of LockWAL called without matching UnlockWAL call.
|
||||
// See also lock_wal_write_token_
|
||||
uint32_t lock_wal_count_;
|
||||
uint32_t lock_wal_count_ = 0;
|
||||
};
|
||||
|
||||
class GetWithTimestampReadCallback : public ReadCallback {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -84,6 +84,7 @@ void DBImpl::TEST_GetFilesMetaData(
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_Current_Manifest_FileNo() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return versions_->manifest_file_number();
|
||||
}
|
||||
|
||||
@@ -224,13 +225,13 @@ void DBImpl::TEST_EndWrite(void* w) {
|
||||
}
|
||||
|
||||
size_t DBImpl::TEST_LogsToFreeSize() {
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
return logs_to_free_.size();
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
return wals_to_free_.size();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_LogfileNumber() {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
return logfile_number_;
|
||||
return cur_wal_number_;
|
||||
}
|
||||
|
||||
void DBImpl::TEST_GetAllBlockCaches(
|
||||
|
||||
+40
-35
@@ -28,7 +28,7 @@ uint64_t DBImpl::MinLogNumberToKeep() {
|
||||
return versions_->min_log_number_to_keep();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::MinLogNumberToRecycle() { return min_log_number_to_recycle_; }
|
||||
uint64_t DBImpl::MinLogNumberToRecycle() { return min_wal_number_to_recycle_; }
|
||||
|
||||
uint64_t DBImpl::MinObsoleteSstNumberToKeep() {
|
||||
mutex_.AssertHeld();
|
||||
@@ -272,77 +272,77 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
|
||||
// logs_ is empty when called during recovery, in which case there can't yet
|
||||
// be any tracked obsolete logs
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
|
||||
if (alive_log_files_.empty() || logs_.empty()) {
|
||||
if (alive_wal_files_.empty() || logs_.empty()) {
|
||||
mutex_.AssertHeld();
|
||||
// We may reach here if the db is DBImplSecondary
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
bool mutex_unlocked = false;
|
||||
if (!alive_log_files_.empty() && !logs_.empty()) {
|
||||
if (!alive_wal_files_.empty() && !logs_.empty()) {
|
||||
uint64_t min_log_number = job_context->log_number;
|
||||
size_t num_alive_log_files = alive_log_files_.size();
|
||||
size_t num_alive_wal_files = alive_wal_files_.size();
|
||||
// find newly obsoleted log files
|
||||
while (alive_log_files_.begin()->number < min_log_number) {
|
||||
auto& earliest = *alive_log_files_.begin();
|
||||
while (alive_wal_files_.begin()->number < min_log_number) {
|
||||
auto& earliest = *alive_wal_files_.begin();
|
||||
if (immutable_db_options_.recycle_log_file_num >
|
||||
log_recycle_files_.size() &&
|
||||
wal_recycle_files_.size() &&
|
||||
earliest.number >= MinLogNumberToRecycle()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"adding log %" PRIu64 " to recycle list\n",
|
||||
earliest.number);
|
||||
log_recycle_files_.push_back(earliest.number);
|
||||
wal_recycle_files_.push_back(earliest.number);
|
||||
} else {
|
||||
job_context->log_delete_files.push_back(earliest.number);
|
||||
}
|
||||
if (job_context->size_log_to_delete == 0) {
|
||||
job_context->prev_total_log_size = total_log_size_;
|
||||
job_context->num_alive_log_files = num_alive_log_files;
|
||||
job_context->prev_wals_total_size = wals_total_size_.LoadRelaxed();
|
||||
job_context->num_alive_wal_files = num_alive_wal_files;
|
||||
}
|
||||
job_context->size_log_to_delete += earliest.size;
|
||||
total_log_size_ -= earliest.size;
|
||||
alive_log_files_.pop_front();
|
||||
wals_total_size_.FetchSubRelaxed(earliest.size);
|
||||
alive_wal_files_.pop_front();
|
||||
|
||||
// Current log should always stay alive since it can't have
|
||||
// number < MinLogNumber().
|
||||
assert(alive_log_files_.size());
|
||||
assert(alive_wal_files_.size());
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
mutex_.Unlock();
|
||||
mutex_unlocked = true;
|
||||
TEST_SYNC_POINT_CALLBACK("FindObsoleteFiles::PostMutexUnlock", nullptr);
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
while (!logs_.empty() && logs_.front().number < min_log_number) {
|
||||
auto& log = logs_.front();
|
||||
if (log.IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
wal_sync_cv_.Wait();
|
||||
// logs_ could have changed while we were waiting.
|
||||
continue;
|
||||
}
|
||||
// This WAL file is not live, so it's OK if we never sync the rest of it.
|
||||
// If it's already closed, then it's been fully synced. If
|
||||
// !background_close_inactive_wals then we need to Close it before
|
||||
// removing from logs_ but not blocking while holding log_write_mutex_.
|
||||
// removing from logs_ but not blocking while holding wal_write_mutex_.
|
||||
if (!immutable_db_options_.background_close_inactive_wals &&
|
||||
log.writer->file()) {
|
||||
// We are taking ownership of and pinning the front entry, so we can
|
||||
// expect it to be the same after releasing and re-acquiring the lock
|
||||
log.PrepareForSync();
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
// TODO: maybe check the return value of Close.
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
auto s = log.writer->file()->Close({});
|
||||
s.PermitUncheckedError();
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
log.writer->PublishIfClosed();
|
||||
assert(&log == &logs_.front());
|
||||
log.FinishSync();
|
||||
log_sync_cv_.SignalAll();
|
||||
wal_sync_cv_.SignalAll();
|
||||
}
|
||||
logs_to_free_.push_back(log.ReleaseWriter());
|
||||
wals_to_free_.push_back(log.ReleaseWriter());
|
||||
logs_.pop_front();
|
||||
}
|
||||
// Current log cannot be obsolete.
|
||||
@@ -350,16 +350,16 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
|
||||
// We're just cleaning up for DB::Write().
|
||||
assert(job_context->logs_to_free.empty());
|
||||
job_context->logs_to_free = logs_to_free_;
|
||||
assert(job_context->wals_to_free.empty());
|
||||
job_context->wals_to_free = wals_to_free_;
|
||||
|
||||
logs_to_free_.clear();
|
||||
log_write_mutex_.Unlock();
|
||||
wals_to_free_.clear();
|
||||
wal_write_mutex_.Unlock();
|
||||
if (mutex_unlocked) {
|
||||
mutex_.Lock();
|
||||
}
|
||||
job_context->log_recycle_files.assign(log_recycle_files_.begin(),
|
||||
log_recycle_files_.end());
|
||||
job_context->log_recycle_files.assign(wal_recycle_files_.begin(),
|
||||
wal_recycle_files_.end());
|
||||
}
|
||||
|
||||
// Delete obsolete files and log status and information of file deletion
|
||||
@@ -431,7 +431,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
state.sst_live.end());
|
||||
std::unordered_set<uint64_t> blob_live_set(state.blob_live.begin(),
|
||||
state.blob_live.end());
|
||||
std::unordered_set<uint64_t> log_recycle_files_set(
|
||||
std::unordered_set<uint64_t> wal_recycle_files_set(
|
||||
state.log_recycle_files.begin(), state.log_recycle_files.end());
|
||||
std::unordered_set<uint64_t> quarantine_files_set(
|
||||
state.files_to_quarantine.begin(), state.files_to_quarantine.end());
|
||||
@@ -491,13 +491,13 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
std::unique(candidate_files.begin(), candidate_files.end()),
|
||||
candidate_files.end());
|
||||
|
||||
if (state.prev_total_log_size > 0) {
|
||||
if (state.prev_wals_total_size > 0) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[JOB %d] Try to delete WAL files size %" PRIu64
|
||||
", prev total WAL file size %" PRIu64
|
||||
", number of live WAL files %" ROCKSDB_PRIszt ".\n",
|
||||
state.job_id, state.size_log_to_delete,
|
||||
state.prev_total_log_size, state.num_alive_log_files);
|
||||
state.prev_wals_total_size, state.num_alive_wal_files);
|
||||
}
|
||||
|
||||
std::vector<std::string> old_info_log_files;
|
||||
@@ -532,7 +532,7 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
optsfile_num2 = std::min(optsfile_num2, state.min_options_file_number);
|
||||
|
||||
// Close WALs before trying to delete them.
|
||||
for (const auto w : state.logs_to_free) {
|
||||
for (const auto w : state.wals_to_free) {
|
||||
// TODO: maybe check the return value of Close.
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
auto s = w->Close({});
|
||||
@@ -559,8 +559,8 @@ void DBImpl::PurgeObsoleteFiles(JobContext& state, bool schedule_only) {
|
||||
case kWalFile:
|
||||
keep = ((number >= state.log_number) ||
|
||||
(number == state.prev_log_number) ||
|
||||
(log_recycle_files_set.find(number) !=
|
||||
log_recycle_files_set.end()));
|
||||
(wal_recycle_files_set.find(number) !=
|
||||
wal_recycle_files_set.end()));
|
||||
break;
|
||||
case kDescriptorFile:
|
||||
// Keep my manifest file, and any newer incarnations'
|
||||
@@ -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:
|
||||
|
||||
@@ -70,9 +70,6 @@ Status DBImplFollower::Recover(
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
s = CheckConsistency();
|
||||
}
|
||||
if (s.ok()) {
|
||||
default_cf_handle_ = new ColumnFamilyHandleImpl(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), this, &mutex_);
|
||||
@@ -296,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();
|
||||
|
||||
+74
-54
@@ -191,12 +191,6 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src,
|
||||
"wal_compression is disabled since only zstd is supported");
|
||||
}
|
||||
|
||||
if (!result.paranoid_checks) {
|
||||
result.skip_checking_sst_file_sizes_on_db_open = true;
|
||||
ROCKS_LOG_INFO(result.info_log,
|
||||
"file size check will be skipped during open.");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -335,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 */,
|
||||
@@ -605,7 +599,7 @@ Status DBImpl::Recover(
|
||||
// allow_ingest_behind does not support Level Compaction,
|
||||
// and per_key_placement can have infinite compaction loop for Level
|
||||
// Compaction. Adjust to_level here just to be safe.
|
||||
if (cfd->ioptions().allow_ingest_behind ||
|
||||
if (cfd->AllowIngestBehind() ||
|
||||
moptions.preclude_last_level_data_seconds > 0) {
|
||||
to_level -= 1;
|
||||
}
|
||||
@@ -694,9 +688,6 @@ Status DBImpl::Recover(
|
||||
s = MaybeUpdateNextFileNumber(recovery_ctx);
|
||||
}
|
||||
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
s = CheckConsistency();
|
||||
}
|
||||
if (s.ok() && !read_only) {
|
||||
// TODO: share file descriptors (FSDirectory) with SetDirectories above
|
||||
std::map<std::string, std::shared_ptr<FSDirectory>> created_dirs;
|
||||
@@ -1119,7 +1110,7 @@ void DBOpenLogRecordReadReporter::Corruption(size_t bytes, const Status& s,
|
||||
static_cast<int>(bytes), s.ToString().c_str());
|
||||
if (status != nullptr && status->ok()) {
|
||||
*status = s;
|
||||
corrupted_log_number_ = log_number;
|
||||
corrupted_wal_number_ = log_number;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1764,8 +1755,12 @@ Status DBImpl::MaybeHandleStopReplayForCorruptionForInconsistency(
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Column family inconsistency: SST file contains data"
|
||||
" beyond the point of corruption.");
|
||||
status = Status::Corruption("SST file is ahead of WALs in CF " +
|
||||
cfd->GetName());
|
||||
status = Status::Corruption(
|
||||
"Column family inconsistency: SST file contains data"
|
||||
" beyond the point of corruption in CF " +
|
||||
cfd->GetName() +
|
||||
". WAL recovery stopped at corruption point, but SST files"
|
||||
" contain newer data.");
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -1902,8 +1897,8 @@ void DBImpl::FinishLogFilesRecovery(int job_id, const Status& status) {
|
||||
}
|
||||
|
||||
Status DBImpl::GetLogSizeAndMaybeTruncate(uint64_t wal_number, bool truncate,
|
||||
LogFileNumberSize* log_ptr) {
|
||||
LogFileNumberSize log(wal_number);
|
||||
WalFileNumberSize* log_ptr) {
|
||||
WalFileNumberSize log(wal_number);
|
||||
std::string fname =
|
||||
LogFileName(immutable_db_options_.GetWalDir(), wal_number);
|
||||
Status s;
|
||||
@@ -1946,27 +1941,27 @@ Status DBImpl::RestoreAliveLogFiles(const std::vector<uint64_t>& wal_numbers) {
|
||||
assert(immutable_db_options_.avoid_flush_during_recovery);
|
||||
// Mark these as alive so they'll be considered for deletion later by
|
||||
// FindObsoleteFiles()
|
||||
total_log_size_ = 0;
|
||||
log_empty_ = false;
|
||||
wals_total_size_.StoreRelaxed(0);
|
||||
wal_empty_ = false;
|
||||
uint64_t min_wal_with_unflushed_data =
|
||||
versions_->MinLogNumberWithUnflushedData();
|
||||
for (auto wal_number : wal_numbers) {
|
||||
if (!allow_2pc() && wal_number < min_wal_with_unflushed_data) {
|
||||
// In non-2pc mode, the WAL files not backing unflushed data are not
|
||||
// alive, thus should not be added to the alive_log_files_.
|
||||
// alive, thus should not be added to the alive_wal_files_.
|
||||
continue;
|
||||
}
|
||||
// We preallocate space for wals, but then after a crash and restart, those
|
||||
// preallocated space are not needed anymore. It is likely only the last
|
||||
// log has such preallocated space, so we only truncate for the last log.
|
||||
LogFileNumberSize log;
|
||||
WalFileNumberSize log;
|
||||
s = GetLogSizeAndMaybeTruncate(
|
||||
wal_number, /*truncate=*/(wal_number == wal_numbers.back()), &log);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
total_log_size_ += log.size;
|
||||
alive_log_files_.push_back(log);
|
||||
wals_total_size_.FetchAddRelaxed(log.size);
|
||||
alive_wal_files_.push_back(log);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -2000,6 +1995,9 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool logical_strip_timestamp =
|
||||
ts_sz > 0 && !cfd->ioptions().persist_user_defined_timestamps;
|
||||
// Note that here we treat flush as level 0 compaction in internal stats
|
||||
InternalStats::CompactionStats flush_stats(CompactionReason::kFlush,
|
||||
1 /* count */);
|
||||
{
|
||||
ScopedArenaPtr<InternalIterator> iter(
|
||||
logical_strip_timestamp
|
||||
@@ -2072,19 +2070,20 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
kMaxSequenceNumber);
|
||||
Version* version = cfd->current();
|
||||
version->Ref();
|
||||
uint64_t num_input_entries = 0;
|
||||
s = BuildTable(dbname_, versions_.get(), immutable_db_options_, tboptions,
|
||||
file_options_for_compaction_, cfd->table_cache(),
|
||||
iter.get(), std::move(range_del_iters), &meta,
|
||||
&blob_file_additions, snapshot_seqs, earliest_snapshot,
|
||||
earliest_write_conflict_snapshot, kMaxSequenceNumber,
|
||||
snapshot_checker, paranoid_file_checks,
|
||||
cfd->internal_stats(), &io_s, io_tracer_,
|
||||
BlobFileCreationReason::kRecovery,
|
||||
nullptr /* seqno_to_time_mapping */, &event_logger_,
|
||||
job_id, nullptr /* table_properties */, write_hint,
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version,
|
||||
&num_input_entries);
|
||||
TableProperties temp_table_proerties;
|
||||
s = BuildTable(
|
||||
dbname_, versions_.get(), immutable_db_options_, tboptions,
|
||||
file_options_for_compaction_, cfd->table_cache(), iter.get(),
|
||||
std::move(range_del_iters), &meta, &blob_file_additions,
|
||||
snapshot_seqs, earliest_snapshot, earliest_write_conflict_snapshot,
|
||||
kMaxSequenceNumber, snapshot_checker, paranoid_file_checks,
|
||||
cfd->internal_stats(), &io_s, io_tracer_,
|
||||
BlobFileCreationReason::kRecovery,
|
||||
nullptr /* seqno_to_time_mapping */, &event_logger_, job_id,
|
||||
&temp_table_proerties /* table_properties */, write_hint,
|
||||
nullptr /*full_history_ts_low*/, &blob_callback_, version,
|
||||
nullptr /* memtable_payload_bytes */,
|
||||
nullptr /* memtable_garbage_bytes */, &flush_stats);
|
||||
version->Unref();
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
@@ -2100,10 +2099,31 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
}
|
||||
|
||||
uint64_t total_num_entries = mem->NumEntries();
|
||||
if (s.ok() && total_num_entries != num_input_entries) {
|
||||
if (s.ok() && total_num_entries != flush_stats.num_input_records) {
|
||||
std::string msg = "Expected " + std::to_string(total_num_entries) +
|
||||
" entries in memtable, but read " +
|
||||
std::to_string(num_input_entries);
|
||||
std::to_string(flush_stats.num_input_records);
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush during recover: %s",
|
||||
cfd->GetName().c_str(), job_id, msg.c_str());
|
||||
if (immutable_db_options_.flush_verify_memtable_count) {
|
||||
s = Status::Corruption(msg);
|
||||
}
|
||||
}
|
||||
// Only verify on table with format collects table properties
|
||||
const auto& mutable_cf_options = cfd->GetLatestMutableCFOptions();
|
||||
if (s.ok() &&
|
||||
(mutable_cf_options.table_factory->IsInstanceOf(
|
||||
TableFactory::kBlockBasedTableName()) ||
|
||||
mutable_cf_options.table_factory->IsInstanceOf(
|
||||
TableFactory::kPlainTableName())) &&
|
||||
flush_stats.num_output_records != temp_table_proerties.num_entries) {
|
||||
std::string msg =
|
||||
"Number of keys in flush output SST files does not match "
|
||||
"number of keys added to the table. Expected " +
|
||||
std::to_string(flush_stats.num_output_records) + " but there are " +
|
||||
std::to_string(temp_table_proerties.num_entries) +
|
||||
" in output SST files";
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush during recover: %s",
|
||||
cfd->GetName().c_str(), job_id, msg.c_str());
|
||||
@@ -2151,25 +2171,25 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
}
|
||||
}
|
||||
|
||||
InternalStats::CompactionStats stats(CompactionReason::kFlush, 1);
|
||||
stats.micros = immutable_db_options_.clock->NowMicros() - start_micros;
|
||||
flush_stats.micros = immutable_db_options_.clock->NowMicros() - start_micros;
|
||||
|
||||
if (has_output) {
|
||||
stats.bytes_written = meta.fd.GetFileSize();
|
||||
stats.num_output_files = 1;
|
||||
flush_stats.bytes_written = meta.fd.GetFileSize();
|
||||
flush_stats.num_output_files = 1;
|
||||
}
|
||||
|
||||
const auto& blobs = edit->GetBlobFileAdditions();
|
||||
for (const auto& blob : blobs) {
|
||||
stats.bytes_written_blob += blob.GetTotalBlobBytes();
|
||||
flush_stats.bytes_written_blob += blob.GetTotalBlobBytes();
|
||||
}
|
||||
|
||||
stats.num_output_files_blob = static_cast<int>(blobs.size());
|
||||
flush_stats.num_output_files_blob = static_cast<int>(blobs.size());
|
||||
|
||||
cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER, stats);
|
||||
cfd->internal_stats()->AddCompactionStats(level, Env::Priority::USER,
|
||||
flush_stats);
|
||||
cfd->internal_stats()->AddCFStats(
|
||||
InternalStats::BYTES_FLUSHED,
|
||||
stats.bytes_written + stats.bytes_written_blob);
|
||||
flush_stats.bytes_written + flush_stats.bytes_written_blob);
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta.fd.GetFileSize());
|
||||
return s;
|
||||
}
|
||||
@@ -2449,18 +2469,18 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
if (s.ok()) {
|
||||
// Prevent log files created by previous instance from being recycled.
|
||||
// They might be in alive_log_file_, and might get recycled otherwise.
|
||||
impl->min_log_number_to_recycle_ = new_log_number;
|
||||
impl->min_wal_number_to_recycle_ = new_log_number;
|
||||
}
|
||||
if (s.ok()) {
|
||||
InstrumentedMutexLock wl(&impl->log_write_mutex_);
|
||||
impl->logfile_number_ = new_log_number;
|
||||
InstrumentedMutexLock wl(&impl->wal_write_mutex_);
|
||||
impl->cur_wal_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
assert(impl->logs_.empty());
|
||||
impl->logs_.emplace_back(new_log_number, new_log);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
impl->alive_log_files_.emplace_back(impl->logfile_number_);
|
||||
impl->alive_wal_files_.emplace_back(impl->cur_wal_number_);
|
||||
// In WritePrepared there could be gap in sequence numbers. This breaks
|
||||
// the trick we use in kPointInTimeRecovery which assumes the first seq in
|
||||
// the log right after the corrupted log is one larger than the last seq
|
||||
@@ -2473,14 +2493,14 @@ Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
if (recovered_seq != kMaxSequenceNumber) {
|
||||
WriteBatch empty_batch;
|
||||
WriteBatchInternal::SetSequence(&empty_batch, recovered_seq);
|
||||
uint64_t log_used, log_size;
|
||||
uint64_t wal_used, log_size;
|
||||
log::Writer* log_writer = impl->logs_.back().writer;
|
||||
LogFileNumberSize& log_file_number_size = impl->alive_log_files_.back();
|
||||
WalFileNumberSize& wal_file_number_size = impl->alive_wal_files_.back();
|
||||
|
||||
assert(log_writer->get_log_number() == log_file_number_size.number);
|
||||
assert(log_writer->get_log_number() == wal_file_number_size.number);
|
||||
impl->mutex_.AssertHeld();
|
||||
s = impl->WriteToWAL(empty_batch, write_options, log_writer, &log_used,
|
||||
&log_size, log_file_number_size, recovered_seq);
|
||||
s = impl->WriteToWAL(empty_batch, write_options, log_writer, &wal_used,
|
||||
&log_size, wal_file_number_size, recovered_seq);
|
||||
if (s.ok()) {
|
||||
// Need to fsync, otherwise it might get lost after a power reset.
|
||||
s = impl->FlushWAL(write_options, false);
|
||||
|
||||
@@ -185,16 +185,10 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& _read_options,
|
||||
? static_cast<const SnapshotImpl*>(read_options.snapshot)->number_
|
||||
: latest_snapshot;
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
auto db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, cfd->ioptions(), super_version->mutable_cf_options,
|
||||
super_version->current, read_seq,
|
||||
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
super_version->version_number, read_callback);
|
||||
auto internal_iter = NewInternalIterator(
|
||||
db_iter->GetReadOptions(), cfd, super_version, db_iter->GetArena(),
|
||||
read_seq, /* allow_unprepared_value */ true, db_iter);
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
return db_iter;
|
||||
return NewArenaWrappedDbIterator(
|
||||
env_, read_options, cfh, super_version, read_seq, read_callback, this,
|
||||
/*expose_blob_index=*/false, /*allow_refresh=*/false,
|
||||
/*allow_mark_memtable_for_flush=*/false);
|
||||
}
|
||||
|
||||
Status DBImplReadOnly::NewIterators(
|
||||
@@ -231,36 +225,32 @@ Status DBImplReadOnly::NewIterators(
|
||||
? static_cast<const SnapshotImpl*>(read_options.snapshot)->number_
|
||||
: latest_snapshot;
|
||||
|
||||
autovector<std::tuple<ColumnFamilyData*, SuperVersion*>> cfd_to_sv;
|
||||
autovector<std::tuple<ColumnFamilyHandleImpl*, SuperVersion*>> cfh_to_sv;
|
||||
|
||||
const bool check_read_ts =
|
||||
read_options.timestamp && read_options.timestamp->size() > 0;
|
||||
for (auto cfh : column_families) {
|
||||
auto* cfd = static_cast_with_check<ColumnFamilyHandleImpl>(cfh)->cfd();
|
||||
auto* sv = cfd->GetSuperVersion()->Ref();
|
||||
cfd_to_sv.emplace_back(cfd, sv);
|
||||
cfh_to_sv.emplace_back(static_cast_with_check<ColumnFamilyHandleImpl>(cfh),
|
||||
sv);
|
||||
if (check_read_ts) {
|
||||
const Status s =
|
||||
FailIfReadCollapsedHistory(cfd, sv, *(read_options.timestamp));
|
||||
if (!s.ok()) {
|
||||
for (auto prev_entry : cfd_to_sv) {
|
||||
for (auto prev_entry : cfh_to_sv) {
|
||||
std::get<1>(prev_entry)->Unref();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(cfd_to_sv.size() == column_families.size());
|
||||
for (auto [cfd, sv] : cfd_to_sv) {
|
||||
assert(cfh_to_sv.size() == column_families.size());
|
||||
for (auto [cfh, sv] : cfh_to_sv) {
|
||||
auto* db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, cfd->ioptions(), sv->mutable_cf_options,
|
||||
sv->current, read_seq,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number, read_callback);
|
||||
auto* internal_iter = NewInternalIterator(
|
||||
db_iter->GetReadOptions(), cfd, sv, db_iter->GetArena(), read_seq,
|
||||
/* allow_unprepared_value */ true, db_iter);
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
env_, read_options, cfh, sv, read_seq, read_callback, this,
|
||||
/*expose_blob_index=*/false, /*allow_refresh=*/false,
|
||||
/*allow_mark_memtable_for_flush=*/false);
|
||||
iterators->push_back(db_iter);
|
||||
}
|
||||
|
||||
|
||||
+726
-91
@@ -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"
|
||||
@@ -49,9 +54,6 @@ Status DBImplSecondary::Recover(
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (immutable_db_options_.paranoid_checks && s.ok()) {
|
||||
s = CheckConsistency();
|
||||
}
|
||||
// Initial max_total_in_memory_state_ before recovery logs.
|
||||
max_total_in_memory_state_ = 0;
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
@@ -566,17 +568,10 @@ ArenaWrappedDBIter* DBImplSecondary::NewIteratorImpl(
|
||||
assert(snapshot == kMaxSequenceNumber);
|
||||
snapshot = versions_->LastSequence();
|
||||
assert(snapshot != kMaxSequenceNumber);
|
||||
auto db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, cfh->cfd()->ioptions(),
|
||||
super_version->mutable_cf_options, super_version->current, snapshot,
|
||||
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
super_version->version_number, read_callback, cfh, expose_blob_index,
|
||||
allow_refresh);
|
||||
auto internal_iter = NewInternalIterator(
|
||||
db_iter->GetReadOptions(), cfh->cfd(), super_version, db_iter->GetArena(),
|
||||
snapshot, /* allow_unprepared_value */ true, db_iter);
|
||||
db_iter->SetIterUnderDBIter(internal_iter);
|
||||
return db_iter;
|
||||
return NewArenaWrappedDbIterator(env_, read_options, cfh, super_version,
|
||||
snapshot, read_callback, this,
|
||||
expose_blob_index, allow_refresh,
|
||||
/*allow_mark_memtable_for_flush=*/false);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::NewIterators(
|
||||
@@ -660,58 +655,15 @@ Status DBImplSecondary::NewIterators(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImplSecondary::CheckConsistency() {
|
||||
mutex_.AssertHeld();
|
||||
Status s = DBImpl::CheckConsistency();
|
||||
// If DBImpl::CheckConsistency() which is stricter returns success, then we
|
||||
// do not need to give a second chance.
|
||||
if (s.ok()) {
|
||||
return s;
|
||||
}
|
||||
// It's possible that DBImpl::CheckConssitency() can fail because the primary
|
||||
// may have removed certain files, causing the GetFileSize(name) call to
|
||||
// fail and returning a PathNotFound. In this case, we take a best-effort
|
||||
// approach and just proceed.
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImplSecondary::CheckConsistency:AfterFirstAttempt", &s);
|
||||
|
||||
if (immutable_db_options_.skip_checking_sst_file_sizes_on_db_open) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
versions_->GetLiveFilesMetaData(&metadata);
|
||||
|
||||
std::string corruption_messages;
|
||||
for (const auto& md : metadata) {
|
||||
// md.name has a leading "/".
|
||||
std::string file_path = md.db_path + md.name;
|
||||
|
||||
uint64_t fsize = 0;
|
||||
s = env_->GetFileSize(file_path, &fsize);
|
||||
if (!s.ok() &&
|
||||
(env_->GetFileSize(Rocks2LevelTableFileName(file_path), &fsize).ok() ||
|
||||
s.IsPathNotFound())) {
|
||||
s = Status::OK();
|
||||
}
|
||||
if (!s.ok()) {
|
||||
corruption_messages +=
|
||||
"Can't access " + md.name + ": " + s.ToString() + "\n";
|
||||
}
|
||||
}
|
||||
return corruption_messages.empty() ? Status::OK()
|
||||
: Status::Corruption(corruption_messages);
|
||||
}
|
||||
|
||||
Status DBImplSecondary::TryCatchUpWithPrimary() {
|
||||
assert(versions_.get() != nullptr);
|
||||
assert(manifest_reader_.get() != nullptr);
|
||||
Status s;
|
||||
// read the manifest and apply new changes to the secondary instance
|
||||
std::unordered_set<ColumnFamilyData*> cfds_changed;
|
||||
JobContext job_context(0, true /*create_superversion*/);
|
||||
{
|
||||
InstrumentedMutexLock lock_guard(&mutex_);
|
||||
assert(manifest_reader_.get() != nullptr);
|
||||
s = static_cast_with_check<ReactiveVersionSet>(versions_.get())
|
||||
->ReadAndApply(&mutex_, &manifest_reader_,
|
||||
manifest_reader_status_.get(), &cfds_changed,
|
||||
@@ -735,13 +687,13 @@ Status DBImplSecondary::TryCatchUpWithPrimary() {
|
||||
// instance
|
||||
if (s.ok()) {
|
||||
s = FindAndRecoverLogFiles(&cfds_changed, &job_context);
|
||||
}
|
||||
if (s.IsPathNotFound()) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Secondary tries to read WAL, but WAL file(s) have already "
|
||||
"been purged by primary.");
|
||||
s = Status::OK();
|
||||
if (s.IsPathNotFound()) {
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Secondary tries to read WAL, but WAL file(s) have already "
|
||||
"been purged by primary.");
|
||||
s = Status::OK();
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
for (auto cfd : cfds_changed) {
|
||||
@@ -831,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();
|
||||
@@ -876,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) {
|
||||
@@ -901,7 +1342,6 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
|
||||
VersionStorageInfo* vstorage = version->storage_info();
|
||||
|
||||
// Use comp_options to reuse some CompactFiles functions
|
||||
CompactionOptions comp_options;
|
||||
comp_options.compression = kDisableCompressionOption;
|
||||
comp_options.output_file_size_limit = MaxFileSizeForLevel(
|
||||
@@ -910,37 +1350,48 @@ 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(
|
||||
immutable_db_options_.info_log,
|
||||
"GetCompactionInputsFromFileNumbers() failed - %s.\n DebugString: %s",
|
||||
s.ToString().c_str(), version->DebugString().c_str());
|
||||
s.ToString().c_str(), version->DebugString(/*hex=*/true).c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
const int job_id = next_job_id_.fetch_add(1);
|
||||
JobContext job_context(job_id, true /*create_superversion*/);
|
||||
std::vector<SequenceNumber> snapshots = input.snapshots;
|
||||
|
||||
// TODO - snapshot_checker support in Remote Compaction
|
||||
job_context.InitSnapshotContext(/*checker=*/nullptr,
|
||||
/*managed_snapshot=*/nullptr,
|
||||
kMaxSequenceNumber, std::move(snapshots));
|
||||
|
||||
// TODO - consider serializing the entire Compaction object and using it as
|
||||
// input instead of recreating it in the remote worker
|
||||
std::unique_ptr<Compaction> c;
|
||||
assert(cfd->compaction_picker());
|
||||
c.reset(cfd->compaction_picker()->CompactFiles(
|
||||
comp_options, input_files, input.output_level, vstorage,
|
||||
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0));
|
||||
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;
|
||||
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_checker));
|
||||
assert(c != nullptr);
|
||||
c->FinalizeInputInfo(version);
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
|
||||
const int job_id = next_job_id_.fetch_add(1);
|
||||
|
||||
// use primary host's db_id for running the compaction, but db_session_id is
|
||||
// using the local one, which is to make sure the unique id is unique from
|
||||
// the remote compactors. Because the id is generated from db_id,
|
||||
@@ -951,17 +1402,19 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
job_id, c.get(), immutable_db_options_, mutable_db_options_,
|
||||
file_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
&log_buffer, output_dir.get(), stats_, &mutex_, &error_handler_,
|
||||
input.snapshots, table_cache_, &event_logger_, dbname_, io_tracer_,
|
||||
&job_context, table_cache_, &event_logger_, dbname_, io_tracer_,
|
||||
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);
|
||||
@@ -969,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;
|
||||
}
|
||||
@@ -991,7 +1456,7 @@ Status DB::OpenAndCompact(
|
||||
}
|
||||
|
||||
// 2. Load the options
|
||||
DBOptions db_options;
|
||||
DBOptions base_db_options;
|
||||
ConfigOptions config_options;
|
||||
config_options.env = override_options.env;
|
||||
config_options.ignore_unknown_options = true;
|
||||
@@ -1004,13 +1469,22 @@ Status DB::OpenAndCompact(
|
||||
std::string options_file_name =
|
||||
OptionsFileName(name, compaction_input.options_file_number);
|
||||
|
||||
s = LoadOptionsFromFile(config_options, options_file_name, &db_options,
|
||||
s = LoadOptionsFromFile(config_options, options_file_name, &base_db_options,
|
||||
&all_column_families);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// 3. Override pointer configurations in DBOptions with
|
||||
// 3. Options to Override
|
||||
// Override serializable configurations from override_options.options_map
|
||||
DBOptions db_options;
|
||||
s = GetDBOptionsFromMap(config_options, base_db_options,
|
||||
override_options.options_map, &db_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// Override options that are directly set as shared ptrs in
|
||||
// CompactionServiceOptionsOverride
|
||||
db_options.env = override_options.env;
|
||||
db_options.file_checksum_gen_factory =
|
||||
@@ -1021,6 +1495,7 @@ Status DB::OpenAndCompact(
|
||||
// We will close the DB after the compaction anyway.
|
||||
// Open as many files as needed for the compaction.
|
||||
db_options.max_open_files = -1;
|
||||
db_options.info_log = override_options.info_log;
|
||||
|
||||
// 4. Filter CFs that are needed for OpenAndCompact()
|
||||
// We do not need to open all column families for the remote compaction.
|
||||
@@ -1030,6 +1505,18 @@ Status DB::OpenAndCompact(
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (auto& cf : all_column_families) {
|
||||
if (cf.name == compaction_input.cf_name) {
|
||||
ColumnFamilyOptions cf_options;
|
||||
// Override serializable configurations from override_options.options_map
|
||||
s = GetColumnFamilyOptionsFromMap(config_options, cf.options,
|
||||
override_options.options_map,
|
||||
&cf_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
cf.options = std::move(cf_options);
|
||||
|
||||
// Override options that are directly set as shared ptrs in
|
||||
// CompactionServiceOptionsOverride
|
||||
cf.options.comparator = override_options.comparator;
|
||||
cf.options.merge_operator = override_options.merge_operator;
|
||||
cf.options.compaction_filter = override_options.compaction_filter;
|
||||
@@ -1041,6 +1528,7 @@ Status DB::OpenAndCompact(
|
||||
override_options.sst_partitioner_factory;
|
||||
cf.options.table_properties_collector_factories =
|
||||
override_options.table_properties_collector_factories;
|
||||
|
||||
column_families.emplace_back(cf);
|
||||
} else if (cf.name == kDefaultColumnFamilyName) {
|
||||
column_families.emplace_back(cf);
|
||||
@@ -1057,6 +1545,9 @@ Status DB::OpenAndCompact(
|
||||
}
|
||||
assert(db);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImplSecondary::OpenAndCompact::AfterOpenAsSecondary:0", db);
|
||||
|
||||
// 6. Find the handle of the Column Family that this will compact
|
||||
ColumnFamilyHandle* cfh = nullptr;
|
||||
for (auto* handle : handles) {
|
||||
@@ -1098,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
|
||||
@@ -248,12 +248,6 @@ class DBImplSecondary : public DBImpl {
|
||||
Status MaybeInitLogReader(uint64_t log_number,
|
||||
log::FragmentBufferedReader** log_reader);
|
||||
|
||||
// Check if all live files exist on file system and that their file sizes
|
||||
// matche to the in-memory records. It is possible that some live files may
|
||||
// have been deleted by the primary. In this case, CheckConsistency() does
|
||||
// not flag the missing file as inconsistency.
|
||||
Status CheckConsistency() override;
|
||||
|
||||
#ifndef NDEBUG
|
||||
Status TEST_CompactWithoutInstallation(const OpenAndCompactOptions& options,
|
||||
ColumnFamilyHandle* cfh,
|
||||
@@ -309,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_;
|
||||
@@ -317,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
|
||||
+277
-179
@@ -157,7 +157,7 @@ Status DBImpl::Write(const WriteOptions& write_options, WriteBatch* my_batch) {
|
||||
if (s.ok()) {
|
||||
s = WriteImpl(write_options, my_batch, /*callback=*/nullptr,
|
||||
/*user_write_cb=*/nullptr,
|
||||
/*log_used=*/nullptr);
|
||||
/*wal_used=*/nullptr);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -190,11 +190,38 @@ Status DBImpl::WriteWithCallback(const WriteOptions& write_options,
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
const WBWIMemTable::SeqnoRange& assigned_seqno,
|
||||
uint64_t prep_log,
|
||||
SequenceNumber last_seqno_after_ingest,
|
||||
bool memtable_updated, bool ignore_missing_cf) {
|
||||
Status DBImpl::IngestWriteBatchWithIndex(
|
||||
const WriteOptions& write_options,
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi) {
|
||||
if (!wbwi) {
|
||||
return Status::InvalidArgument("Batch is nullptr!");
|
||||
}
|
||||
if (!write_options.disableWAL) {
|
||||
return Status::NotSupported(
|
||||
"IngestWriteBatchWithIndex does not support disableWAL=true");
|
||||
}
|
||||
Status s;
|
||||
if (write_options.protection_bytes_per_key > 0) {
|
||||
s = WriteBatchInternal::UpdateProtectionInfo(
|
||||
wbwi->GetWriteBatch(), write_options.protection_bytes_per_key);
|
||||
}
|
||||
if (s.ok()) {
|
||||
WriteBatch dummy_empty_batch;
|
||||
s = WriteImpl(
|
||||
write_options, /*updates=*/&dummy_empty_batch, /*callback=*/nullptr,
|
||||
/*user_write_cb=*/nullptr, /*log_used=*/nullptr, /*log_ref=*/0,
|
||||
/*disable_memtable=*/false, /*seq_used=*/nullptr,
|
||||
/*batch_cnt=*/0, /*pre_release_callback=*/nullptr,
|
||||
/*post_memtable_callback=*/nullptr, /*wbwi=*/wbwi);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::IngestWBWIAsMemtable(
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
const WBWIMemTable::SeqnoRange& assigned_seqno, uint64_t min_prep_log,
|
||||
SequenceNumber last_seqno_after_ingest, bool memtable_updated,
|
||||
bool ignore_missing_cf) {
|
||||
// Keys in new memtable have seqno > last_seqno_after_ingest >= keys in wbwi.
|
||||
assert(assigned_seqno.upper_bound <= last_seqno_after_ingest);
|
||||
// Keys in the current memtable have seqno <= LastSequence() < keys in wbwi.
|
||||
@@ -238,12 +265,30 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
wbwi_memtable->AssignSequenceNumbers(assigned_seqno);
|
||||
// This is needed to keep the WAL that contains Prepare alive until
|
||||
// committed data in this memtable is persisted.
|
||||
wbwi_memtable->SetMinPrepLog(prep_log);
|
||||
wbwi_memtable->SetMinPrepLog(min_prep_log);
|
||||
memtables.push_back(wbwi_memtable);
|
||||
cfd->Ref();
|
||||
cfds.push_back(cfd);
|
||||
}
|
||||
|
||||
autovector<ColumnFamilyData*> cfds_for_atomic_flush;
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
SelectColumnFamiliesForAtomicFlush(&cfds_for_atomic_flush);
|
||||
for (auto cfd : cfds_for_atomic_flush) {
|
||||
bool found = false;
|
||||
for (auto existing_cfd : cfds) {
|
||||
if (existing_cfd == cfd) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
cfd->Ref();
|
||||
cfds.push_back(cfd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop writes to the DB by entering both write threads
|
||||
WriteThread::Writer nonmem_w;
|
||||
if (two_write_queues_) {
|
||||
@@ -253,15 +298,16 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
|
||||
// Switch memtable and add WBWIMemTables
|
||||
Status s;
|
||||
for (size_t i = 0; i < memtables.size(); ++i) {
|
||||
assert(!immutable_db_options_.atomic_flush);
|
||||
// NOTE: to support atomic flush, need to call
|
||||
// SelectColumnFamiliesForAtomicFlush()
|
||||
for (size_t i = 0; i < cfds.size(); ++i) {
|
||||
WriteContext write_context;
|
||||
// TODO: not switch on empty memtable, may need to update metadata
|
||||
// like NextLogNumber(), earliest_seqno and memtable id.
|
||||
s = SwitchMemtable(cfds[i], &write_context, memtables[i],
|
||||
last_seqno_after_ingest);
|
||||
if (i < memtables.size()) {
|
||||
s = SwitchMemtable(cfds[i], &write_context, memtables[i],
|
||||
last_seqno_after_ingest);
|
||||
} else {
|
||||
s = SwitchMemtable(cfds[i], &write_context);
|
||||
}
|
||||
if (!s.ok()) {
|
||||
// SwitchMemtable() can only fail if a new WAL is to be created, this
|
||||
// should only happen for the first call to SwitchMemtable(). log will
|
||||
@@ -301,9 +347,18 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
continue;
|
||||
}
|
||||
cfd->imm()->FlushRequested();
|
||||
if (!immutable_db_options_.atomic_flush) {
|
||||
FlushRequest flush_req;
|
||||
// TODO: a new flush reason for ingesting memtable
|
||||
GenerateFlushRequest({cfd}, FlushReason::kExternalFileIngestion,
|
||||
&flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
AssignAtomicFlushSeq(cfds);
|
||||
FlushRequest flush_req;
|
||||
// TODO: a new flush reason for ingesting memtable
|
||||
GenerateFlushRequest({cfd}, FlushReason::kExternalFileIngestion,
|
||||
GenerateFlushRequest(cfds, FlushReason::kExternalFileIngestion,
|
||||
&flush_req);
|
||||
EnqueuePendingFlush(flush_req);
|
||||
}
|
||||
@@ -314,13 +369,12 @@ Status DBImpl::IngestWBWI(std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
|
||||
Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
UserWriteCallback* user_write_cb, uint64_t* log_used,
|
||||
UserWriteCallback* user_write_cb, uint64_t* wal_used,
|
||||
uint64_t log_ref, bool disable_memtable,
|
||||
uint64_t* seq_used, size_t batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback,
|
||||
PostMemTableCallback* post_memtable_callback,
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi,
|
||||
uint64_t prep_log) {
|
||||
std::shared_ptr<WriteBatchWithIndex> wbwi) {
|
||||
assert(!seq_per_batch_ || batch_cnt != 0);
|
||||
assert(my_batch == nullptr || my_batch->Count() == 0 ||
|
||||
write_options.protection_bytes_per_key == 0 ||
|
||||
@@ -409,9 +463,17 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
return Status::NotSupported(
|
||||
"DeleteRange is not compatible with row cache.");
|
||||
}
|
||||
// Whether the WBWI is from transaction commit or a direct write
|
||||
// (IngestWriteBatchWithIndex())
|
||||
bool ingest_wbwi_for_commit = false;
|
||||
if (wbwi) {
|
||||
assert(prep_log > 0);
|
||||
// Used only in WriteCommittedTxn::CommitInternal() with no `callback`.
|
||||
if (my_batch->HasCommit()) {
|
||||
ingest_wbwi_for_commit = true;
|
||||
assert(log_ref);
|
||||
} else {
|
||||
// Only supports disableWAL for directly ingesting WBWI for now.
|
||||
assert(write_options.disableWAL);
|
||||
}
|
||||
assert(!callback);
|
||||
if (immutable_db_options_.unordered_write) {
|
||||
return Status::NotSupported(
|
||||
@@ -421,9 +483,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
return Status::NotSupported(
|
||||
"Ingesting WriteBatch does not support pipelined_write");
|
||||
}
|
||||
if (immutable_db_options_.atomic_flush) {
|
||||
if (!wbwi->GetOverwriteKey()) {
|
||||
return Status::NotSupported(
|
||||
"Ingesting WriteBatch does not support atomic_flush");
|
||||
"WriteBatchWithIndex ingestion requires overwrite_key=true");
|
||||
}
|
||||
}
|
||||
// Otherwise IsLatestPersistentState optimization does not make sense
|
||||
@@ -444,7 +506,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// they don't consume sequence.
|
||||
return WriteImplWALOnly(
|
||||
&nonmem_write_thread_, write_options, my_batch, callback, user_write_cb,
|
||||
log_used, log_ref, seq_used, batch_cnt, pre_release_callback,
|
||||
wal_used, log_ref, seq_used, batch_cnt, pre_release_callback,
|
||||
assign_order, kDontPublishLastSeq, disable_memtable);
|
||||
}
|
||||
|
||||
@@ -458,7 +520,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// sequence in in increasing order, iii) call pre_release_callback serially
|
||||
Status status = WriteImplWALOnly(
|
||||
&write_thread_, write_options, my_batch, callback, user_write_cb,
|
||||
log_used, log_ref, &seq, sub_batch_cnt, pre_release_callback,
|
||||
wal_used, log_ref, &seq, sub_batch_cnt, pre_release_callback,
|
||||
kDoAssignOrder, kDoPublishLastSeq, disable_memtable);
|
||||
TEST_SYNC_POINT("DBImpl::WriteImpl:UnorderedWriteAfterWriteWAL");
|
||||
if (!status.ok()) {
|
||||
@@ -477,7 +539,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (immutable_db_options_.enable_pipelined_write) {
|
||||
return PipelinedWriteImpl(write_options, my_batch, callback, user_write_cb,
|
||||
log_used, log_ref, disable_memtable, seq_used);
|
||||
wal_used, log_ref, disable_memtable, seq_used);
|
||||
}
|
||||
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
@@ -524,16 +586,19 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
assert(tmp_s.ok());
|
||||
}
|
||||
}
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
if (w.status.ok()) { // Don't publish a partial batch write
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
} else {
|
||||
HandleMemTableInsertFailure(w.status);
|
||||
}
|
||||
write_thread_.ExitAsBatchGroupFollower(&w);
|
||||
}
|
||||
assert(w.state == WriteThread::STATE_COMPLETED);
|
||||
// STATE_COMPLETED conditional below handles exit
|
||||
}
|
||||
if (w.state == WriteThread::STATE_COMPLETED) {
|
||||
if (log_used != nullptr) {
|
||||
*log_used = w.log_used;
|
||||
if (wal_used != nullptr) {
|
||||
*wal_used = w.wal_used;
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
@@ -549,7 +614,8 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// when it finds suitable, and finish them in the same write batch.
|
||||
// This is how a write job could be done by the other writer.
|
||||
WriteContext write_context;
|
||||
LogContext log_context(write_options.sync);
|
||||
// FIXME: also check disableWAL like others?
|
||||
WalContext wal_context(write_options.sync);
|
||||
WriteThread::WriteGroup write_group;
|
||||
bool in_parallel_group = false;
|
||||
uint64_t last_sequence = kMaxSequenceNumber;
|
||||
@@ -563,7 +629,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// PreprocessWrite does its own perf timing.
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
status = PreprocessWrite(write_options, &log_context, &write_context);
|
||||
status = PreprocessWrite(write_options, &wal_context, &write_context);
|
||||
if (!two_write_queues_) {
|
||||
// Assign it after ::PreprocessWrite since the sequence might advance
|
||||
// inside it by WriteRecoverableState
|
||||
@@ -631,7 +697,13 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
continue;
|
||||
}
|
||||
// TODO: maybe handle the tracing status?
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
if (wbwi && !ingest_wbwi_for_commit) {
|
||||
// for transaction write, tracer only needs the commit marker which
|
||||
// is in writer->batch
|
||||
tracer_->Write(wbwi->GetWriteBatch()).PermitUncheckedError();
|
||||
} else {
|
||||
tracer_->Write(writer->batch).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -689,22 +761,21 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
if (!two_write_queues_) {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
assert(log_context.log_file_number_size);
|
||||
LogFileNumberSize& log_file_number_size =
|
||||
*(log_context.log_file_number_size);
|
||||
assert(wal_context.wal_file_number_size);
|
||||
wal_context.prev_size = wal_context.writer->file()->GetFileSize();
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
io_s =
|
||||
WriteToWAL(write_group, log_context.writer, log_used,
|
||||
log_context.need_log_sync, log_context.need_log_dir_sync,
|
||||
last_sequence + 1, log_file_number_size);
|
||||
io_s = WriteGroupToWAL(write_group, wal_context.writer, wal_used,
|
||||
wal_context.need_wal_sync,
|
||||
wal_context.need_wal_dir_sync, last_sequence + 1,
|
||||
*wal_context.wal_file_number_size);
|
||||
}
|
||||
} else {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
// LastAllocatedSequence is increased inside WriteToWAL under
|
||||
// wal_write_mutex_ to ensure ordered events in WAL
|
||||
io_s = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
|
||||
seq_inc);
|
||||
io_s = ConcurrentWriteGroupToWAL(write_group, wal_used, &last_sequence,
|
||||
seq_inc);
|
||||
} else {
|
||||
// Otherwise we inc seq number for memtable writes
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
@@ -716,16 +787,16 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
last_sequence += seq_inc;
|
||||
// Seqno assigned to this write are [current_sequence, last_sequence]
|
||||
|
||||
if (log_context.need_log_sync) {
|
||||
if (wal_context.need_wal_sync) {
|
||||
VersionEdit synced_wals;
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
if (status.ok()) {
|
||||
MarkLogsSynced(logfile_number_, log_context.need_log_dir_sync,
|
||||
MarkLogsSynced(cur_wal_number_, wal_context.need_wal_dir_sync,
|
||||
&synced_wals);
|
||||
} else {
|
||||
MarkLogsNotSynced(logfile_number_);
|
||||
MarkLogsNotSynced(cur_wal_number_);
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
if (status.ok() && synced_wals.IsWalAddition()) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
@@ -760,7 +831,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
writer->sequence = next_sequence;
|
||||
if (writer->pre_release_callback) {
|
||||
Status ws = writer->pre_release_callback->Callback(
|
||||
writer->sequence, disable_memtable, writer->log_used, index++,
|
||||
writer->sequence, disable_memtable, writer->wal_used, index++,
|
||||
pre_release_callback_cnt);
|
||||
if (!ws.ok()) {
|
||||
status = pre_release_cb_status = ws;
|
||||
@@ -785,8 +856,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
write_group, current_sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, &trim_history_scheduler_,
|
||||
write_options.ignore_missing_column_families,
|
||||
0 /*recovery_log_number*/, this, parallel, seq_per_batch_,
|
||||
batch_per_txn_);
|
||||
0 /*recovery_log_number*/, this, seq_per_batch_, batch_per_txn_);
|
||||
} else {
|
||||
write_group.last_sequence = last_sequence;
|
||||
write_thread_.LaunchParallelMemTableWriters(&write_group);
|
||||
@@ -834,12 +904,13 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// handle exit, false means somebody else did
|
||||
should_exit_batch_group = write_thread_.CompleteParallelMemTableWriter(&w);
|
||||
}
|
||||
if (wbwi) {
|
||||
if (status.ok() && w.status.ok()) {
|
||||
if (wbwi && status.ok() && w.status.ok()) {
|
||||
uint32_t wbwi_count = wbwi->GetWriteBatch()->Count();
|
||||
// skip empty batch case
|
||||
if (wbwi_count) {
|
||||
// w.batch contains (potentially empty) commit time batch updates,
|
||||
// only ingest wbwi if w.batch is applied to memtable successfully
|
||||
uint32_t memtable_update_count = w.batch->Count();
|
||||
uint32_t wbwi_count = wbwi->GetWriteBatch()->Count();
|
||||
// Seqno assigned to this write are [last_seq + 1 - seq_inc, last_seq].
|
||||
// seq_inc includes w.batch (memtable updates) and wbwi
|
||||
// w.batch gets first `memtable_update_count` sequence numbers.
|
||||
@@ -852,10 +923,12 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
if (two_write_queues_) {
|
||||
assert(ub <= versions_->LastAllocatedSequence());
|
||||
}
|
||||
status = IngestWBWI(wbwi, {/*lower_bound=*/lb, /*upper_bound=*/ub},
|
||||
prep_log, last_sequence,
|
||||
/*memtable_updated=*/memtable_update_count > 0,
|
||||
write_options.ignore_missing_column_families);
|
||||
status =
|
||||
IngestWBWIAsMemtable(wbwi, {/*lower_bound=*/lb, /*upper_bound=*/ub},
|
||||
/*min_prep_log=*/log_ref, last_sequence,
|
||||
/*memtable_updated=*/memtable_update_count > 0,
|
||||
write_options.ignore_missing_column_families);
|
||||
RecordTick(stats_, NUMBER_WBWI_INGEST);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -873,9 +946,19 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
// Note: if we are to resume after non-OK statuses we need to revisit how
|
||||
// we react to non-OK statuses here.
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
if (w.status.ok()) { // Don't publish a partial batch write
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
}
|
||||
if (!w.status.ok()) {
|
||||
if (wal_context.prev_size < SIZE_MAX) {
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
if (logs_.back().number == wal_context.wal_file_number_size->number) {
|
||||
logs_.back().SetAttemptTruncateSize(wal_context.prev_size);
|
||||
}
|
||||
}
|
||||
HandleMemTableInsertFailure(w.status);
|
||||
}
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
write_thread_.ExitAsBatchGroupLeader(write_group, status);
|
||||
}
|
||||
|
||||
@@ -888,7 +971,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
UserWriteCallback* user_write_cb,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
uint64_t* wal_used, uint64_t log_ref,
|
||||
bool disable_memtable, uint64_t* seq_used) {
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
StopWatch write_sw(immutable_db_options_.clock, stats_, DB_WRITE);
|
||||
@@ -905,10 +988,10 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
if (w.callback && !w.callback->AllowWriteBatching()) {
|
||||
write_thread_.WaitForMemTableWriters();
|
||||
}
|
||||
LogContext log_context(!write_options.disableWAL && write_options.sync);
|
||||
WalContext wal_context(!write_options.disableWAL && write_options.sync);
|
||||
// PreprocessWrite does its own perf timing.
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
w.status = PreprocessWrite(write_options, &log_context, &write_context);
|
||||
w.status = PreprocessWrite(write_options, &wal_context, &write_context);
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
// This can set non-OK status if callback fail.
|
||||
@@ -977,13 +1060,13 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
wal_write_group.size - 1);
|
||||
RecordTick(stats_, WRITE_DONE_BY_OTHER, wal_write_group.size - 1);
|
||||
}
|
||||
assert(log_context.log_file_number_size);
|
||||
LogFileNumberSize& log_file_number_size =
|
||||
*(log_context.log_file_number_size);
|
||||
io_s =
|
||||
WriteToWAL(wal_write_group, log_context.writer, log_used,
|
||||
log_context.need_log_sync, log_context.need_log_dir_sync,
|
||||
current_sequence, log_file_number_size);
|
||||
assert(wal_context.wal_file_number_size);
|
||||
WalFileNumberSize& wal_file_number_size =
|
||||
*(wal_context.wal_file_number_size);
|
||||
io_s = WriteGroupToWAL(wal_write_group, wal_context.writer, wal_used,
|
||||
wal_context.need_wal_sync,
|
||||
wal_context.need_wal_dir_sync, current_sequence,
|
||||
wal_file_number_size);
|
||||
w.status = io_s;
|
||||
}
|
||||
|
||||
@@ -995,13 +1078,13 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
VersionEdit synced_wals;
|
||||
if (log_context.need_log_sync) {
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
if (wal_context.need_wal_sync) {
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
if (w.status.ok()) {
|
||||
MarkLogsSynced(logfile_number_, log_context.need_log_dir_sync,
|
||||
MarkLogsSynced(cur_wal_number_, wal_context.need_wal_dir_sync,
|
||||
&synced_wals);
|
||||
} else {
|
||||
MarkLogsNotSynced(logfile_number_);
|
||||
MarkLogsNotSynced(cur_wal_number_);
|
||||
}
|
||||
}
|
||||
if (w.status.ok() && synced_wals.IsWalAddition()) {
|
||||
@@ -1031,8 +1114,13 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
memtable_write_group, w.sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, &trim_history_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
|
||||
false /*concurrent_memtable_writes*/, seq_per_batch_, batch_per_txn_);
|
||||
versions_->SetLastSequence(memtable_write_group.last_sequence);
|
||||
seq_per_batch_, batch_per_txn_);
|
||||
if (memtable_write_group.status
|
||||
.ok()) { // Don't publish a partial batch write
|
||||
versions_->SetLastSequence(memtable_write_group.last_sequence);
|
||||
} else {
|
||||
HandleMemTableInsertFailure(memtable_write_group.status);
|
||||
}
|
||||
write_thread_.ExitAsMemTableWriter(&w, memtable_write_group);
|
||||
}
|
||||
} else {
|
||||
@@ -1061,8 +1149,11 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (write_thread_.CompleteParallelMemTableWriter(&w)) {
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
versions_->SetLastSequence(w.write_group->last_sequence);
|
||||
if (w.status.ok()) { // Don't publish a partial batch write
|
||||
versions_->SetLastSequence(w.write_group->last_sequence);
|
||||
} else {
|
||||
HandleMemTableInsertFailure(w.status);
|
||||
}
|
||||
write_thread_.ExitAsMemTableWriter(&w, *w.write_group);
|
||||
}
|
||||
}
|
||||
@@ -1134,7 +1225,7 @@ Status DBImpl::UnorderedWriteMemtable(const WriteOptions& write_options,
|
||||
Status DBImpl::WriteImplWALOnly(
|
||||
WriteThread* write_thread, const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
UserWriteCallback* user_write_cb, uint64_t* log_used,
|
||||
UserWriteCallback* user_write_cb, uint64_t* wal_used,
|
||||
const uint64_t log_ref, uint64_t* seq_used, const size_t sub_batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback, const AssignOrder assign_order,
|
||||
const PublishLastSeq publish_last_seq, const bool disable_memtable) {
|
||||
@@ -1147,8 +1238,8 @@ Status DBImpl::WriteImplWALOnly(
|
||||
write_thread->JoinBatchGroup(&w);
|
||||
assert(w.state != WriteThread::STATE_PARALLEL_MEMTABLE_WRITER);
|
||||
if (w.state == WriteThread::STATE_COMPLETED) {
|
||||
if (log_used != nullptr) {
|
||||
*log_used = w.log_used;
|
||||
if (wal_used != nullptr) {
|
||||
*wal_used = w.wal_used;
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
@@ -1164,10 +1255,10 @@ Status DBImpl::WriteImplWALOnly(
|
||||
|
||||
// TODO(myabandeh): Make preliminary checks thread-safe so we could do them
|
||||
// without paying the cost of obtaining the mutex.
|
||||
LogContext log_context;
|
||||
WalContext wal_context;
|
||||
WriteContext write_context;
|
||||
Status status =
|
||||
PreprocessWrite(write_options, &log_context, &write_context);
|
||||
PreprocessWrite(write_options, &wal_context, &write_context);
|
||||
WriteStatusCheckOnLocked(status);
|
||||
|
||||
if (!status.ok()) {
|
||||
@@ -1264,8 +1355,8 @@ Status DBImpl::WriteImplWALOnly(
|
||||
}
|
||||
Status status;
|
||||
if (!write_options.disableWAL) {
|
||||
IOStatus io_s =
|
||||
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
IOStatus io_s = ConcurrentWriteGroupToWAL(write_group, wal_used,
|
||||
&last_sequence, seq_inc);
|
||||
status = io_s;
|
||||
// last_sequence may not be set if there is an error
|
||||
// This error checking and return is moved up to avoid using uninitialized
|
||||
@@ -1317,7 +1408,7 @@ Status DBImpl::WriteImplWALOnly(
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
Status ws = writer->pre_release_callback->Callback(
|
||||
writer->sequence, disable_memtable, writer->log_used, index++,
|
||||
writer->sequence, disable_memtable, writer->wal_used, index++,
|
||||
pre_release_callback_cnt);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
@@ -1386,24 +1477,22 @@ void DBImpl::WALIOStatusCheck(const IOStatus& io_status) {
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::MemTableInsertStatusCheck(const Status& status) {
|
||||
// A non-OK status here indicates that the state implied by the
|
||||
// WAL has diverged from the in-memory state. This could be
|
||||
// because of a corrupt write_batch (very bad), or because the
|
||||
// client specified an invalid column family and didn't specify
|
||||
// ignore_missing_column_families.
|
||||
if (!status.ok()) {
|
||||
mutex_.Lock();
|
||||
assert(!error_handler_.IsBGWorkStopped());
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
void DBImpl::HandleMemTableInsertFailure(const Status& status) {
|
||||
assert(!status.ok());
|
||||
// A non-OK status on memtable insert indicates that the state implied by the
|
||||
// WAL has diverged from the in-memory state. This could be because of a
|
||||
// corrupt write_batch (very bad), or because the client specified an invalid
|
||||
// column family and didn't specify ignore_missing_column_families.
|
||||
mutex_.Lock();
|
||||
assert(!error_handler_.IsBGWorkStopped());
|
||||
error_handler_.SetBGError(status, BackgroundErrorReason::kMemTable);
|
||||
mutex_.Unlock();
|
||||
}
|
||||
|
||||
Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
LogContext* log_context,
|
||||
WalContext* wal_context,
|
||||
WriteContext* write_context) {
|
||||
assert(write_context != nullptr && log_context != nullptr);
|
||||
assert(write_context != nullptr && wal_context != nullptr);
|
||||
Status status;
|
||||
|
||||
if (error_handler_.IsDBStopped()) {
|
||||
@@ -1413,7 +1502,8 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_GUARD(write_scheduling_flushes_compactions_time);
|
||||
|
||||
if (UNLIKELY(status.ok() && total_log_size_ > GetMaxTotalWalSize())) {
|
||||
if (UNLIKELY(status.ok() &&
|
||||
wals_total_size_.LoadRelaxed() > GetMaxTotalWalSize())) {
|
||||
assert(versions_);
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
const ColumnFamilySet* const column_families =
|
||||
@@ -1482,17 +1572,17 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
WriteBufferManagerStallWrites();
|
||||
}
|
||||
}
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
if (status.ok() && log_context->need_log_sync) {
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
if (status.ok() && wal_context->need_wal_sync) {
|
||||
// Wait until the parallel syncs are finished. Any sync process has to sync
|
||||
// the front log too so it is enough to check the status of front()
|
||||
// We do a while loop since log_sync_cv_ is signalled when any sync is
|
||||
// We do a while loop since wal_sync_cv_ is signalled when any sync is
|
||||
// finished
|
||||
// Note: there does not seem to be a reason to wait for parallel sync at
|
||||
// this early step but it is not important since parallel sync (SyncWAL) and
|
||||
// need_log_sync are usually not used together.
|
||||
// need_wal_sync are usually not used together.
|
||||
while (logs_.front().IsSyncing()) {
|
||||
log_sync_cv_.Wait();
|
||||
wal_sync_cv_.Wait();
|
||||
}
|
||||
for (auto& log : logs_) {
|
||||
// This is just to prevent the logs to be synced by a parallel SyncWAL
|
||||
@@ -1503,12 +1593,12 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
log.PrepareForSync();
|
||||
}
|
||||
} else {
|
||||
log_context->need_log_sync = false;
|
||||
wal_context->need_wal_sync = false;
|
||||
}
|
||||
log_context->writer = logs_.back().writer;
|
||||
log_context->need_log_dir_sync =
|
||||
log_context->need_log_dir_sync && !log_dir_synced_;
|
||||
log_context->log_file_number_size = std::addressof(alive_log_files_.back());
|
||||
wal_context->writer = logs_.back().writer;
|
||||
wal_context->need_wal_dir_sync =
|
||||
wal_context->need_wal_dir_sync && !wal_dir_synced_;
|
||||
wal_context->wal_file_number_size = std::addressof(alive_wal_files_.back());
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -1559,12 +1649,12 @@ Status DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
}
|
||||
|
||||
// When two_write_queues_ is disabled, this function is called from the only
|
||||
// write thread. Otherwise this must be called holding log_write_mutex_.
|
||||
// write thread. Otherwise this must be called holding wal_write_mutex_.
|
||||
IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
const WriteOptions& write_options,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
log::Writer* log_writer, uint64_t* wal_used,
|
||||
uint64_t* log_size,
|
||||
LogFileNumberSize& log_file_number_size,
|
||||
WalFileNumberSize& wal_file_number_size,
|
||||
SequenceNumber sequence) {
|
||||
assert(log_size != nullptr);
|
||||
|
||||
@@ -1576,7 +1666,7 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
}
|
||||
*log_size = log_entry.size();
|
||||
// When two_write_queues_ WriteToWAL has to be protected from concurretn calls
|
||||
// from the two queues anyway and log_write_mutex_ is already held. Otherwise
|
||||
// from the two queues anyway and wal_write_mutex_ is already held. Otherwise
|
||||
// if manual_wal_flush_ is enabled we need to protect log_writer->AddRecord
|
||||
// from possible concurrent calls via the FlushWAL by the application.
|
||||
const bool needs_locking = manual_wal_flush_ && !two_write_queues_;
|
||||
@@ -1584,7 +1674,7 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
// manual_wal_flush_ feature (by UNLIKELY) instead of the more common case
|
||||
// when we do not need any locking.
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
}
|
||||
IOStatus io_s = log_writer->MaybeAddUserDefinedTimestampSizeRecord(
|
||||
write_options, versions_->GetColumnFamiliesTimestampSizeForRecord());
|
||||
@@ -1594,23 +1684,24 @@ IOStatus DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
io_s = log_writer->AddRecord(write_options, log_entry, sequence);
|
||||
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
}
|
||||
if (log_used != nullptr) {
|
||||
*log_used = logfile_number_;
|
||||
if (wal_used != nullptr) {
|
||||
*wal_used = cur_wal_number_;
|
||||
assert(*wal_used == wal_file_number_size.number);
|
||||
}
|
||||
total_log_size_ += log_entry.size();
|
||||
log_file_number_size.AddSize(*log_size);
|
||||
log_empty_ = false;
|
||||
wals_total_size_.FetchAddRelaxed(log_entry.size());
|
||||
wal_file_number_size.AddSize(*log_size);
|
||||
wal_empty_ = false;
|
||||
|
||||
return io_s;
|
||||
}
|
||||
|
||||
IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
bool need_log_sync, bool need_log_dir_sync,
|
||||
SequenceNumber sequence,
|
||||
LogFileNumberSize& log_file_number_size) {
|
||||
IOStatus DBImpl::WriteGroupToWAL(const WriteThread::WriteGroup& write_group,
|
||||
log::Writer* log_writer, uint64_t* wal_used,
|
||||
bool need_wal_sync, bool need_wal_dir_sync,
|
||||
SequenceNumber sequence,
|
||||
WalFileNumberSize& wal_file_number_size) {
|
||||
IOStatus io_s;
|
||||
assert(!two_write_queues_);
|
||||
assert(!write_group.leader->disable_wal);
|
||||
@@ -1625,10 +1716,10 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
}
|
||||
|
||||
if (merged_batch == write_group.leader->batch) {
|
||||
write_group.leader->log_used = logfile_number_;
|
||||
write_group.leader->wal_used = cur_wal_number_;
|
||||
} else if (write_with_wal > 1) {
|
||||
for (auto writer : write_group) {
|
||||
writer->log_used = logfile_number_;
|
||||
writer->wal_used = cur_wal_number_;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1640,14 +1731,14 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
WriteOptions write_options;
|
||||
write_options.rate_limiter_priority =
|
||||
write_group.leader->rate_limiter_priority;
|
||||
io_s = WriteToWAL(*merged_batch, write_options, log_writer, log_used,
|
||||
&log_size, log_file_number_size, sequence);
|
||||
io_s = WriteToWAL(*merged_batch, write_options, log_writer, wal_used,
|
||||
&log_size, wal_file_number_size, sequence);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
|
||||
if (io_s.ok() && need_log_sync) {
|
||||
if (io_s.ok() && need_wal_sync) {
|
||||
StopWatch sw(immutable_db_options_.clock, stats_, WAL_FILE_SYNC_MICROS);
|
||||
// It's safe to access logs_ with unlocked mutex_ here because:
|
||||
// - we've set getting_synced=true for all logs,
|
||||
@@ -1657,15 +1748,15 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
// - as long as other threads don't modify it, it's safe to read
|
||||
// from std::deque from multiple threads concurrently.
|
||||
//
|
||||
// Sync operation should work with locked log_write_mutex_, because:
|
||||
// Sync operation should work with locked wal_write_mutex_, because:
|
||||
// when DBOptions.manual_wal_flush_ is set,
|
||||
// FlushWAL function will be invoked by another thread.
|
||||
// if without locked log_write_mutex_, the log file may get data
|
||||
// if without locked wal_write_mutex_, the log file may get data
|
||||
// corruption
|
||||
|
||||
const bool needs_locking = manual_wal_flush_ && !two_write_queues_;
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
}
|
||||
|
||||
if (io_s.ok()) {
|
||||
@@ -1688,10 +1779,10 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
}
|
||||
|
||||
if (UNLIKELY(needs_locking)) {
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (io_s.ok() && need_log_dir_sync) {
|
||||
if (io_s.ok() && need_wal_dir_sync) {
|
||||
// We only sync WAL directory the first time WAL syncing is
|
||||
// requested, so that in case users never turn on WAL sync,
|
||||
// we can avoid the disk I/O in the write code path.
|
||||
@@ -1706,7 +1797,7 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
}
|
||||
if (io_s.ok()) {
|
||||
auto stats = default_cf_internal_stats_;
|
||||
if (need_log_sync) {
|
||||
if (need_wal_sync) {
|
||||
stats->AddDBStats(InternalStats::kIntStatsWalFileSynced, 1);
|
||||
RecordTick(stats_, WAL_FILE_SYNCED);
|
||||
}
|
||||
@@ -1723,8 +1814,8 @@ IOStatus DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
return io_s;
|
||||
}
|
||||
|
||||
IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
const WriteThread::WriteGroup& write_group, uint64_t* log_used,
|
||||
IOStatus DBImpl::ConcurrentWriteGroupToWAL(
|
||||
const WriteThread::WriteGroup& write_group, uint64_t* wal_used,
|
||||
SequenceNumber* last_sequence, size_t seq_inc) {
|
||||
IOStatus io_s;
|
||||
|
||||
@@ -1741,14 +1832,14 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
return io_s;
|
||||
}
|
||||
|
||||
// We need to lock log_write_mutex_ since logs_ and alive_log_files might be
|
||||
// We need to lock wal_write_mutex_ since logs_ and alive_wal_files might be
|
||||
// pushed back concurrently
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
if (merged_batch == write_group.leader->batch) {
|
||||
write_group.leader->log_used = logfile_number_;
|
||||
write_group.leader->wal_used = cur_wal_number_;
|
||||
} else if (write_with_wal > 1) {
|
||||
for (auto writer : write_group) {
|
||||
writer->log_used = logfile_number_;
|
||||
writer->wal_used = cur_wal_number_;
|
||||
}
|
||||
}
|
||||
*last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
@@ -1756,9 +1847,9 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
WriteBatchInternal::SetSequence(merged_batch, sequence);
|
||||
|
||||
log::Writer* log_writer = logs_.back().writer;
|
||||
LogFileNumberSize& log_file_number_size = alive_log_files_.back();
|
||||
WalFileNumberSize& wal_file_number_size = alive_wal_files_.back();
|
||||
|
||||
assert(log_writer->get_log_number() == log_file_number_size.number);
|
||||
assert(log_writer->get_log_number() == wal_file_number_size.number);
|
||||
|
||||
uint64_t log_size;
|
||||
|
||||
@@ -1766,13 +1857,13 @@ IOStatus DBImpl::ConcurrentWriteToWAL(
|
||||
WriteOptions write_options;
|
||||
write_options.rate_limiter_priority =
|
||||
write_group.leader->rate_limiter_priority;
|
||||
io_s = WriteToWAL(*merged_batch, write_options, log_writer, log_used,
|
||||
&log_size, log_file_number_size, sequence);
|
||||
io_s = WriteToWAL(*merged_batch, write_options, log_writer, wal_used,
|
||||
&log_size, wal_file_number_size, sequence);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
|
||||
if (io_s.ok()) {
|
||||
const bool concurrent = true;
|
||||
@@ -1800,7 +1891,7 @@ Status DBImpl::WriteRecoverableState() {
|
||||
bool dont_care_bool;
|
||||
SequenceNumber next_seq;
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
}
|
||||
SequenceNumber seq;
|
||||
if (two_write_queues_) {
|
||||
@@ -1815,13 +1906,17 @@ Status DBImpl::WriteRecoverableState() {
|
||||
0 /*recovery_log_number*/, this, false /* concurrent_memtable_writes */,
|
||||
&next_seq, &dont_care_bool, seq_per_batch_);
|
||||
auto last_seq = next_seq - 1;
|
||||
if (two_write_queues_) {
|
||||
versions_->FetchAddLastAllocatedSequence(last_seq - seq);
|
||||
versions_->SetLastPublishedSequence(last_seq);
|
||||
if (status.ok()) { // Don't publish a partial batch write
|
||||
if (two_write_queues_) {
|
||||
versions_->FetchAddLastAllocatedSequence(last_seq - seq);
|
||||
versions_->SetLastPublishedSequence(last_seq);
|
||||
}
|
||||
versions_->SetLastSequence(last_seq);
|
||||
} else {
|
||||
HandleMemTableInsertFailure(status);
|
||||
}
|
||||
versions_->SetLastSequence(last_seq);
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
}
|
||||
if (status.ok() && recoverable_state_pre_release_callback_) {
|
||||
const bool DISABLE_MEMTABLE = true;
|
||||
@@ -1893,7 +1988,10 @@ void DBImpl::AssignAtomicFlushSeq(const autovector<ColumnFamilyData*>& cfds) {
|
||||
assert(immutable_db_options_.atomic_flush);
|
||||
auto seq = versions_->LastSequence();
|
||||
for (auto cfd : cfds) {
|
||||
cfd->imm()->AssignAtomicFlushSeq(seq);
|
||||
// cfd can be nullptr, see ScheduleFlushes()
|
||||
if (cfd) {
|
||||
cfd->imm()->AssignAtomicFlushSeq(seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1902,11 +2000,11 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
assert(write_context != nullptr);
|
||||
Status status;
|
||||
|
||||
if (alive_log_files_.begin()->getting_flushed) {
|
||||
if (alive_wal_files_.begin()->getting_flushed) {
|
||||
return status;
|
||||
}
|
||||
|
||||
auto oldest_alive_log = alive_log_files_.begin()->number;
|
||||
auto oldest_alive_log = alive_wal_files_.begin()->number;
|
||||
bool flush_wont_release_oldest_log = false;
|
||||
if (allow_2pc()) {
|
||||
auto oldest_log_with_uncommitted_prep =
|
||||
@@ -1936,14 +2034,14 @@ Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
// transactions then we cannot flush this log until those transactions are
|
||||
// commited.
|
||||
unable_to_release_oldest_log_ = false;
|
||||
alive_log_files_.begin()->getting_flushed = true;
|
||||
alive_wal_files_.begin()->getting_flushed = true;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(
|
||||
immutable_db_options_.info_log,
|
||||
"Flushing all column families with data in WAL number %" PRIu64
|
||||
". Total log size is %" PRIu64 " while max_total_wal_size is %" PRIu64,
|
||||
oldest_alive_log, total_log_size_.load(), GetMaxTotalWalSize());
|
||||
oldest_alive_log, wals_total_size_.LoadRelaxed(), GetMaxTotalWalSize());
|
||||
// no need to refcount because drop is happening in write thread, so can't
|
||||
// happen while we're in the write thread
|
||||
autovector<ColumnFamilyData*> cfds;
|
||||
@@ -2413,21 +2511,21 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
// Do this without holding the dbmutex lock.
|
||||
assert(versions_->prev_log_number() == 0);
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
}
|
||||
bool creating_new_log = !log_empty_;
|
||||
bool creating_new_log = !wal_empty_;
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
}
|
||||
uint64_t recycle_log_number = 0;
|
||||
// If file deletion is disabled, don't recycle logs since it'll result in
|
||||
// the file getting renamed
|
||||
if (creating_new_log && immutable_db_options_.recycle_log_file_num &&
|
||||
!log_recycle_files_.empty() && IsFileDeletionsEnabled()) {
|
||||
recycle_log_number = log_recycle_files_.front();
|
||||
!wal_recycle_files_.empty() && IsFileDeletionsEnabled()) {
|
||||
recycle_log_number = wal_recycle_files_.front();
|
||||
}
|
||||
uint64_t new_log_number =
|
||||
creating_new_log ? versions_->NewFileNumber() : logfile_number_;
|
||||
creating_new_log ? versions_->NewFileNumber() : cur_wal_number_;
|
||||
// For use outside of holding DB mutex
|
||||
const MutableCFOptions mutable_cf_options_copy =
|
||||
cfd->GetLatestMutableCFOptions();
|
||||
@@ -2453,14 +2551,14 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
mutex_.Unlock();
|
||||
if (creating_new_log) {
|
||||
PredecessorWALInfo info;
|
||||
log_write_mutex_.Lock();
|
||||
wal_write_mutex_.Lock();
|
||||
if (!logs_.empty()) {
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
info = PredecessorWALInfo(cur_log_writer->get_log_number(),
|
||||
cur_log_writer->file()->GetFileSize(),
|
||||
cur_log_writer->GetLastSeqnoRecorded());
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
wal_write_mutex_.Unlock();
|
||||
// TODO: Write buffer size passed in should be max of all CF's instead
|
||||
// of mutable_cf_options.write_buffer_size.
|
||||
io_s = CreateWAL(write_options, new_log_number, recycle_log_number,
|
||||
@@ -2501,11 +2599,11 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
// concurrent full purges don't delete the file while we're recycling it.
|
||||
// To achieve that we hold the old log number in the recyclable list until
|
||||
// after it has been renamed.
|
||||
assert(log_recycle_files_.front() == recycle_log_number);
|
||||
log_recycle_files_.pop_front();
|
||||
assert(wal_recycle_files_.front() == recycle_log_number);
|
||||
wal_recycle_files_.pop_front();
|
||||
}
|
||||
if (s.ok() && creating_new_log) {
|
||||
InstrumentedMutexLock l(&log_write_mutex_);
|
||||
InstrumentedMutexLock l(&wal_write_mutex_);
|
||||
assert(new_log != nullptr);
|
||||
if (!logs_.empty()) {
|
||||
// Alway flush the buffer of the last log before switching to a new one
|
||||
@@ -2527,11 +2625,11 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
logfile_number_ = new_log_number;
|
||||
log_empty_ = true;
|
||||
log_dir_synced_ = false;
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.emplace_back(logfile_number_);
|
||||
cur_wal_number_ = new_log_number;
|
||||
wal_empty_ = true;
|
||||
wal_dir_synced_ = false;
|
||||
logs_.emplace_back(cur_wal_number_, new_log);
|
||||
alive_wal_files_.emplace_back(cur_wal_number_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2562,7 +2660,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
// obsolete. So we should track the WAL obsoletion event before actually
|
||||
// updating the empty CF's log number.
|
||||
uint64_t min_wal_number_to_keep =
|
||||
versions_->PreComputeMinLogNumberWithUnflushedData(logfile_number_);
|
||||
versions_->PreComputeMinLogNumberWithUnflushedData(cur_wal_number_);
|
||||
if (min_wal_number_to_keep >
|
||||
versions_->GetWalSet().GetMinWalNumberToKeep()) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
@@ -2597,7 +2695,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
|
||||
for (auto cf : empty_cfs) {
|
||||
if (cf->IsEmpty()) {
|
||||
cf->SetLogNumber(logfile_number_);
|
||||
cf->SetLogNumber(cur_wal_number_);
|
||||
// MEMPURGE: No need to change this, because new adds
|
||||
// should still receive new sequence numbers.
|
||||
cf->mem()->SetCreationSeq(versions_->LastSequence());
|
||||
@@ -2614,14 +2712,14 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
// advance the log number. no need to persist this in the manifest
|
||||
if (cf->IsEmpty()) {
|
||||
if (creating_new_log) {
|
||||
cf->SetLogNumber(logfile_number_);
|
||||
cf->SetLogNumber(cur_wal_number_);
|
||||
}
|
||||
cf->mem()->SetCreationSeq(versions_->LastSequence());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfd->mem()->SetNextLogNumber(logfile_number_);
|
||||
cfd->mem()->SetNextLogNumber(cur_wal_number_);
|
||||
assert(new_mem != nullptr);
|
||||
cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_);
|
||||
if (new_imm) {
|
||||
@@ -2633,7 +2731,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
// we always try to flush all immutable memtable. For atomic flush, these
|
||||
// two memtables will be marked eligible for flush in the same call to
|
||||
// AssignAtomicFlushSeq().
|
||||
new_imm->SetNextLogNumber(logfile_number_);
|
||||
new_imm->SetNextLogNumber(cur_wal_number_);
|
||||
cfd->imm()->Add(new_imm, &context->memtables_to_free_);
|
||||
}
|
||||
new_mem->Ref();
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/testutil.h"
|
||||
|
||||
+146
-29
@@ -9,7 +9,6 @@
|
||||
|
||||
#include "db/db_iter.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
@@ -42,9 +41,8 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* cmp, InternalIterator* iter,
|
||||
const Version* version, SequenceNumber s, bool arena_mode,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
|
||||
bool expose_blob_index)
|
||||
bool expose_blob_index, ReadOnlyMemTable* active_mem)
|
||||
: prefix_extractor_(mutable_cf_options.prefix_extractor.get()),
|
||||
env_(_env),
|
||||
clock_(ioptions.clock),
|
||||
@@ -58,11 +56,21 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
read_callback_(read_callback),
|
||||
sequence_(s),
|
||||
statistics_(ioptions.stats),
|
||||
max_skip_(max_sequential_skip_in_iterations),
|
||||
max_skip_(mutable_cf_options.max_sequential_skip_in_iterations),
|
||||
max_skippable_internal_keys_(read_options.max_skippable_internal_keys),
|
||||
num_internal_keys_skipped_(0),
|
||||
iterate_lower_bound_(read_options.iterate_lower_bound),
|
||||
iterate_upper_bound_(read_options.iterate_upper_bound),
|
||||
cfh_(cfh),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
timestamp_lb_(read_options.iter_start_ts),
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0),
|
||||
active_mem_(active_mem),
|
||||
memtable_seqno_lb_(kMaxSequenceNumber),
|
||||
memtable_op_scan_flush_trigger_(0),
|
||||
avg_op_scan_flush_trigger_(0),
|
||||
iter_step_since_seek_(1),
|
||||
mem_hidden_op_scanned_since_seek_(0),
|
||||
direction_(kForward),
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
@@ -76,11 +84,7 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
expose_blob_index_(expose_blob_index),
|
||||
allow_unprepared_value_(read_options.allow_unprepared_value),
|
||||
is_blob_(false),
|
||||
arena_mode_(arena_mode),
|
||||
cfh_(cfh),
|
||||
timestamp_ub_(read_options.timestamp),
|
||||
timestamp_lb_(read_options.iter_start_ts),
|
||||
timestamp_size_(timestamp_ub_ ? timestamp_ub_->size() : 0) {
|
||||
arena_mode_(arena_mode) {
|
||||
RecordTick(statistics_, NO_ITERATOR_CREATED);
|
||||
if (pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
@@ -94,6 +98,25 @@ DBIter::DBIter(Env* _env, const ReadOptions& read_options,
|
||||
// prefix_seek_opt_in_only should force total_order_seek whereever the caller
|
||||
// is duplicating the original ReadOptions
|
||||
assert(!ioptions.prefix_seek_opt_in_only || read_options.total_order_seek);
|
||||
if (active_mem_) {
|
||||
// FIXME: GetEarliestSequenceNumber() may return a seqno that is one smaller
|
||||
// than the smallest seqno in the memtable. This violates its comment and
|
||||
// entries with that seqno may not be in the active memtable. Before it's
|
||||
// fixed, we use GetFirstSequenceNumber() for more accurate result.
|
||||
memtable_seqno_lb_ = active_mem_->IsEmpty()
|
||||
? active_mem_->GetEarliestSequenceNumber()
|
||||
: active_mem_->GetFirstSequenceNumber();
|
||||
memtable_op_scan_flush_trigger_ =
|
||||
mutable_cf_options.memtable_op_scan_flush_trigger;
|
||||
if (memtable_op_scan_flush_trigger_) {
|
||||
// avg_op_scan_flush_trigger_ requires memtable_op_scan_flush_trigger_ > 0
|
||||
avg_op_scan_flush_trigger_ =
|
||||
mutable_cf_options.memtable_avg_op_scan_flush_trigger;
|
||||
}
|
||||
} else {
|
||||
// memtable_op_scan_flush_trigger_ and avg_op_scan_flush_trigger_ are
|
||||
// initialized to 0(disabled) as default.
|
||||
}
|
||||
}
|
||||
|
||||
Status DBIter::GetProperty(std::string prop_name, std::string* prop) {
|
||||
@@ -155,6 +178,7 @@ void DBIter::Next() {
|
||||
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
||||
local_stats_.skip_count_--;
|
||||
num_internal_keys_skipped_ = 0;
|
||||
iter_step_since_seek_++;
|
||||
bool ok = true;
|
||||
if (direction_ == kReverse) {
|
||||
is_key_seqnum_zero_ = false;
|
||||
@@ -369,6 +393,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
// to one.
|
||||
bool reseek_done = false;
|
||||
|
||||
uint64_t mem_hidden_op_scanned = 0;
|
||||
do {
|
||||
// Will update is_key_seqnum_zero_ as soon as we parsed the current key
|
||||
// but we need to save the previous value to be used in the loop.
|
||||
@@ -425,6 +450,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) <= 0) {
|
||||
num_skipped++; // skip this entry
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
MarkMemtableForFlushForPerOpTrigger(mem_hidden_op_scanned);
|
||||
} else {
|
||||
assert(!skipping_saved_key ||
|
||||
CompareKeyForSkip(ikey_.user_key, saved_key_.GetUserKey()) > 0);
|
||||
@@ -446,6 +472,7 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
!iter_.iter()->IsKeyPinned() /* copy */);
|
||||
skipping_saved_key = true;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
MarkMemtableForFlushForPerOpTrigger(mem_hidden_op_scanned);
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
@@ -484,7 +511,6 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
|
||||
valid_ = true;
|
||||
return true;
|
||||
break;
|
||||
case kTypeMerge:
|
||||
if (!PrepareValueInternal()) {
|
||||
return false;
|
||||
@@ -496,7 +522,6 @@ bool DBIter::FindNextUserEntryInternal(bool skipping_saved_key,
|
||||
current_entry_is_merged_ = true;
|
||||
valid_ = true;
|
||||
return MergeValuesNewToOld(); // Go to a different state machine
|
||||
break;
|
||||
default:
|
||||
valid_ = false;
|
||||
status_ = Status::Corruption(
|
||||
@@ -1097,7 +1122,6 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
case kTypeValuePreferredSeqno:
|
||||
SetValueAndColumnsFromPlain(pinned_value_);
|
||||
@@ -1224,6 +1248,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
|
||||
if (timestamp_lb_ != nullptr) {
|
||||
saved_key_.SetInternalKey(ikey);
|
||||
} else {
|
||||
saved_key_.SetUserKey(ikey.user_key);
|
||||
}
|
||||
|
||||
valid_ = true;
|
||||
@@ -1539,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;
|
||||
@@ -1568,6 +1698,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
|
||||
// Seek the inner iterator based on the target key.
|
||||
{
|
||||
@@ -1644,6 +1775,7 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
|
||||
// Seek the inner iterator based on the target key.
|
||||
{
|
||||
@@ -1705,6 +1837,7 @@ void DBIter::SeekToFirst() {
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
ClearSavedValue();
|
||||
is_key_seqnum_zero_ = false;
|
||||
|
||||
@@ -1768,6 +1901,7 @@ void DBIter::SeekToLast() {
|
||||
ResetBlobData();
|
||||
ResetValueAndColumns();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
ClearSavedValue();
|
||||
is_key_seqnum_zero_ = false;
|
||||
|
||||
@@ -1790,21 +1924,4 @@ void DBIter::SeekToLast() {
|
||||
StripTimestampFromUserKey(saved_key_.GetUserKey(), timestamp_size_)));
|
||||
}
|
||||
}
|
||||
|
||||
Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* user_key_comparator,
|
||||
InternalIterator* internal_iter, const Version* version,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback,
|
||||
ColumnFamilyHandleImpl* cfh, bool expose_blob_index) {
|
||||
DBIter* db_iter = new DBIter(
|
||||
env, read_options, ioptions, mutable_cf_options, user_key_comparator,
|
||||
internal_iter, version, sequence, false,
|
||||
max_sequential_skip_in_iterations, read_callback, cfh, expose_blob_index);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+89
-31
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "memory/arena.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -57,6 +56,34 @@ class Version;
|
||||
// numbers, deletion markers, overwrites, etc.
|
||||
class DBIter final : public Iterator {
|
||||
public:
|
||||
// Return a new DBIter that reads from `internal_iter` at the specified
|
||||
// `sequence` number.
|
||||
//
|
||||
// @param active_mem Pointer to the active memtable that `internal_iter`
|
||||
// is reading from. If not null, the memtable can be marked for flush
|
||||
// according to options mutable_cf_options.memtable_op_scan_flush_trigger
|
||||
// and mutable_cf_options.memtable_avg_op_scan_flush_trigger.
|
||||
// @param arena_mode If true, the DBIter will be allocated from the arena.
|
||||
static DBIter* NewIter(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* user_key_comparator,
|
||||
InternalIterator* internal_iter,
|
||||
const Version* version, const SequenceNumber& sequence,
|
||||
ReadCallback* read_callback,
|
||||
ReadOnlyMemTable* active_mem,
|
||||
ColumnFamilyHandleImpl* cfh = nullptr,
|
||||
bool expose_blob_index = false,
|
||||
Arena* arena = nullptr) {
|
||||
void* mem = arena ? arena->AllocateAligned(sizeof(DBIter))
|
||||
: operator new(sizeof(DBIter));
|
||||
DBIter* db_iter = new (mem)
|
||||
DBIter(env, read_options, ioptions, mutable_cf_options,
|
||||
user_key_comparator, internal_iter, version, sequence, arena,
|
||||
read_callback, cfh, expose_blob_index, active_mem);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
// The following is grossly complicated. TODO: clean it up
|
||||
// Which direction is the iterator currently moving?
|
||||
// (1) When moving forward:
|
||||
@@ -113,19 +140,12 @@ class DBIter final : public Iterator {
|
||||
uint64_t skip_count_;
|
||||
};
|
||||
|
||||
DBIter(Env* _env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
|
||||
InternalIterator* iter, const Version* version, SequenceNumber s,
|
||||
bool arena_mode, uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, ColumnFamilyHandleImpl* cfh,
|
||||
bool expose_blob_index);
|
||||
|
||||
// No copying allowed
|
||||
DBIter(const DBIter&) = delete;
|
||||
void operator=(const DBIter&) = delete;
|
||||
|
||||
~DBIter() override {
|
||||
MarkMemtableForFlushForAvgTrigger();
|
||||
ThreadStatus::OperationType cur_op_type =
|
||||
ThreadStatusUtil::GetThreadOperation();
|
||||
ThreadStatusUtil::SetThreadOperation(
|
||||
@@ -220,7 +240,18 @@ class DBIter final : public Iterator {
|
||||
|
||||
bool PrepareValue() override;
|
||||
|
||||
void Prepare(const MultiScanArgs& scan_opts) override;
|
||||
Status ValidateScanOptions(const MultiScanArgs& multiscan_opts) const;
|
||||
|
||||
private:
|
||||
DBIter(Env* _env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Comparator* cmp,
|
||||
InternalIterator* iter, const Version* version, SequenceNumber s,
|
||||
bool arena_mode, ReadCallback* read_callback,
|
||||
ColumnFamilyHandleImpl* cfh, bool expose_blob_index,
|
||||
ReadOnlyMemTable* active_mem);
|
||||
|
||||
class BlobReader {
|
||||
public:
|
||||
BlobReader(const Version* version, ReadTier read_tier,
|
||||
@@ -379,6 +410,36 @@ class DBIter final : public Iterator {
|
||||
return true;
|
||||
}
|
||||
|
||||
void MarkMemtableForFlushForAvgTrigger() {
|
||||
if (avg_op_scan_flush_trigger_ &&
|
||||
mem_hidden_op_scanned_since_seek_ >= memtable_op_scan_flush_trigger_ &&
|
||||
mem_hidden_op_scanned_since_seek_ >=
|
||||
static_cast<uint64_t>(iter_step_since_seek_) *
|
||||
avg_op_scan_flush_trigger_) {
|
||||
assert(memtable_op_scan_flush_trigger_ > 0);
|
||||
active_mem_->MarkForFlush();
|
||||
avg_op_scan_flush_trigger_ = 0;
|
||||
memtable_op_scan_flush_trigger_ = 0;
|
||||
}
|
||||
iter_step_since_seek_ = 1;
|
||||
mem_hidden_op_scanned_since_seek_ = 0;
|
||||
}
|
||||
|
||||
void MarkMemtableForFlushForPerOpTrigger(uint64_t& mem_hidden_op_scanned) {
|
||||
if (memtable_op_scan_flush_trigger_ &&
|
||||
ikey_.sequence >= memtable_seqno_lb_) {
|
||||
if (++mem_hidden_op_scanned >= memtable_op_scan_flush_trigger_) {
|
||||
active_mem_->MarkForFlush();
|
||||
// Turn off the flush trigger checks.
|
||||
memtable_op_scan_flush_trigger_ = 0;
|
||||
avg_op_scan_flush_trigger_ = 0;
|
||||
}
|
||||
if (avg_op_scan_flush_trigger_) {
|
||||
++mem_hidden_op_scanned_since_seek_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SliceTransform* prefix_extractor_;
|
||||
Env* const env_;
|
||||
SystemClock* clock_;
|
||||
@@ -425,6 +486,25 @@ class DBIter final : public Iterator {
|
||||
IterKey prefix_;
|
||||
|
||||
Status status_;
|
||||
Slice lazy_blob_index_;
|
||||
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
ColumnFamilyHandleImpl* cfh_;
|
||||
const Slice* const timestamp_ub_;
|
||||
const Slice* const timestamp_lb_;
|
||||
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_;
|
||||
uint32_t avg_op_scan_flush_trigger_;
|
||||
uint32_t iter_step_since_seek_;
|
||||
uint32_t mem_hidden_op_scanned_since_seek_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
@@ -443,29 +523,7 @@ class DBIter final : public Iterator {
|
||||
// the stacked BlobDB implementation is used, false otherwise.
|
||||
bool expose_blob_index_;
|
||||
bool allow_unprepared_value_;
|
||||
Slice lazy_blob_index_;
|
||||
bool is_blob_;
|
||||
bool arena_mode_;
|
||||
// List of operands for merge operator.
|
||||
MergeContext merge_context_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
ColumnFamilyHandleImpl* cfh_;
|
||||
const Slice* const timestamp_ub_;
|
||||
const Slice* const timestamp_lb_;
|
||||
const size_t timestamp_size_;
|
||||
std::string saved_timestamp_;
|
||||
};
|
||||
|
||||
// Return a new iterator that converts internal keys (yielded by
|
||||
// "*internal_iter") that were live at the specified `sequence` number
|
||||
// into appropriate user keys.
|
||||
Iterator* NewDBIterator(
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const Comparator* user_key_comparator, InternalIterator* internal_iter,
|
||||
const Version* version, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, ReadCallback* read_callback,
|
||||
ColumnFamilyHandleImpl* cfh = nullptr, bool expose_blob_index = false);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -528,12 +528,11 @@ TEST_F(DBIteratorStressTest, StressTest) {
|
||||
internal_iter->target_hidden_fraction =
|
||||
target_hidden_fraction;
|
||||
internal_iter->trace = trace;
|
||||
db_iter.reset(NewDBIterator(
|
||||
db_iter.reset(DBIter::NewIter(
|
||||
env_, ropt, ImmutableOptions(options),
|
||||
MutableCFOptions(options), BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, sequence,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /*read_callback*/));
|
||||
internal_iter, /*version=*/nullptr, sequence,
|
||||
nullptr /*read_callback*/, /*active_mem=*/nullptr));
|
||||
}
|
||||
|
||||
// Do a random operation. It's important to do it on ref_it
|
||||
|
||||
+168
-228
@@ -259,11 +259,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -294,11 +293,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -322,11 +320,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -356,11 +353,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -393,11 +389,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
@@ -425,11 +420,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 7 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_TRUE(GetPerfLevel() == kEnableCount);
|
||||
@@ -465,11 +459,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 4 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -492,11 +485,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
@@ -517,11 +509,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -554,11 +545,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
ReadOptions ro;
|
||||
ro.iterate_upper_bound = &prefix;
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 7 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
SetPerfLevel(kEnableCount);
|
||||
ASSERT_TRUE(GetPerfLevel() == kEnableCount);
|
||||
@@ -586,11 +576,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -631,11 +620,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
@@ -664,11 +652,10 @@ TEST_F(DBIteratorTest, DBIteratorPrevNext) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
@@ -696,11 +683,10 @@ TEST_F(DBIteratorTest, DBIteratorEmpty) {
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
@@ -710,11 +696,10 @@ TEST_F(DBIteratorTest, DBIteratorEmpty) {
|
||||
TestIterator* internal_iter = new TestIterator(BytewiseComparator());
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
@@ -735,11 +720,10 @@ TEST_F(DBIteratorTest, DBIteratorUseSkipCountSkips) {
|
||||
}
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
2 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
2 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
@@ -782,11 +766,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->Finish();
|
||||
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i + 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -820,11 +804,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->AddPut("c", "200");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i + 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -851,11 +835,10 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
internal_iter->AddPut("c", "200");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 202 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -886,11 +869,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
}
|
||||
internal_iter->AddPut("c", "200");
|
||||
internal_iter->Finish();
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
@@ -906,11 +889,10 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
}
|
||||
internal_iter->AddPut("c", "200");
|
||||
internal_iter->Finish();
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 200 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
@@ -944,11 +926,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
}
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i + 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -981,11 +963,11 @@ TEST_F(DBIteratorTest, DBIteratorUseSkip) {
|
||||
}
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, i + 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -1033,11 +1015,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.max_skippable_internal_keys = 0;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1081,11 +1062,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1127,11 +1107,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1167,11 +1146,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1204,11 +1182,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1236,11 +1213,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1275,11 +1251,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.max_skippable_internal_keys = 2;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1314,11 +1289,11 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.max_skippable_internal_keys = i;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 * i + 1 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1369,11 +1344,10 @@ TEST_F(DBIteratorTest, DBIteratorSkipInternalKeys) {
|
||||
|
||||
options.max_sequential_skip_in_iterations = 1000;
|
||||
ro.max_skippable_internal_keys = i;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, MutableCFOptions(options), BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 * i + 1 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -1412,11 +1386,11 @@ TEST_F(DBIteratorTest, DBIteratorTimedPutBasic) {
|
||||
internal_iter->AddTimedPut("d", "3", /*write_unix_time=*/0);
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
options.max_sequential_skip_in_iterations = 1;
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
7 /* sequence */, /*max_sequential_skip_in_iterations*/ 1,
|
||||
nullptr /* read_callback */));
|
||||
7 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1463,11 +1437,10 @@ TEST_F(DBIteratorTest, DBIterator1) {
|
||||
internal_iter->AddMerge("b", "2");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
1 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
1 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1493,11 +1466,10 @@ TEST_F(DBIteratorTest, DBIterator2) {
|
||||
internal_iter->AddMerge("b", "2");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
0 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
0 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1519,11 +1491,10 @@ TEST_F(DBIteratorTest, DBIterator3) {
|
||||
internal_iter->AddMerge("b", "2");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
2 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
2 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1545,11 +1516,10 @@ TEST_F(DBIteratorTest, DBIterator4) {
|
||||
internal_iter->AddMerge("b", "2");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
4 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
4 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1580,11 +1550,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1605,11 +1574,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 1 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1630,11 +1598,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1655,11 +1622,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 3 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1680,11 +1646,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 4 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1705,11 +1670,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 5 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1730,11 +1694,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 6 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1753,11 +1716,10 @@ TEST_F(DBIteratorTest, DBIterator5) {
|
||||
internal_iter->AddMerge("a", "merge_2");
|
||||
internal_iter->AddPut("b", "val_b");
|
||||
internal_iter->Finish();
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->Seek("b");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
@@ -1785,11 +1747,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1810,11 +1771,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 1 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1835,11 +1795,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1860,11 +1819,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 3 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(!db_iter->Valid());
|
||||
ASSERT_OK(db_iter->status());
|
||||
@@ -1881,11 +1839,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 4 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1906,11 +1863,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 5 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1931,11 +1887,10 @@ TEST_F(DBIteratorTest, DBIterator6) {
|
||||
internal_iter->AddMerge("a", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 6 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -1976,11 +1931,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 0 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -2013,11 +1967,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 2 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -2056,11 +2009,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 4 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -2099,11 +2051,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 5 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -2147,11 +2098,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 6 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -2196,11 +2146,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 7 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -2239,11 +2188,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 9 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -2288,11 +2236,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 13 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -2338,11 +2285,10 @@ TEST_F(DBIteratorTest, DBIterator7) {
|
||||
internal_iter->AddDeletion("c");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ioptions, mutable_cf_options, BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 14 /* sequence */,
|
||||
options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -2371,11 +2317,10 @@ TEST_F(DBIteratorTest, DBIterator8) {
|
||||
internal_iter->AddPut("b", "0");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
@@ -2403,11 +2348,11 @@ TEST_F(DBIteratorTest, DBIterator9) {
|
||||
internal_iter->AddMerge("d", "merge_6");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
std::unique_ptr<Iterator> db_iter(
|
||||
DBIter::NewIter(env_, ro, ImmutableOptions(options),
|
||||
MutableCFOptions(options), BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -2471,11 +2416,10 @@ TEST_F(DBIteratorTest, DBIterator10) {
|
||||
internal_iter->AddPut("d", "4");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->Seek("c");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
@@ -2512,10 +2456,10 @@ TEST_F(DBIteratorTest, SeekToLastOccurrenceSeq0) {
|
||||
internal_iter->AddPut("b", "2");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, 0 /* force seek */, nullptr /* read_callback */));
|
||||
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -2542,11 +2486,10 @@ TEST_F(DBIteratorTest, DBIterator11) {
|
||||
internal_iter->AddMerge("b", "2");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
1 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
1 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToFirst();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "a");
|
||||
@@ -2571,10 +2514,10 @@ TEST_F(DBIteratorTest, DBIterator12) {
|
||||
internal_iter->AddSingleDeletion("b");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, 0 /* force seek */, nullptr /* read_callback */));
|
||||
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->SeekToLast();
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "c");
|
||||
@@ -2610,11 +2553,11 @@ TEST_F(DBIteratorTest, DBIterator13) {
|
||||
internal_iter->AddPut(key, "8");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
options.max_sequential_skip_in_iterations = 3;
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
2 /* sequence */, 3 /* max_sequential_skip_in_iterations */,
|
||||
nullptr /* read_callback */));
|
||||
2 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->Seek("b");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), key);
|
||||
@@ -2640,11 +2583,11 @@ TEST_F(DBIteratorTest, DBIterator14) {
|
||||
internal_iter->AddPut("c", "9");
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
options.max_sequential_skip_in_iterations = 1;
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
4 /* sequence */, 1 /* max_sequential_skip_in_iterations */,
|
||||
nullptr /* read_callback */));
|
||||
4 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
db_iter->Seek("b");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
ASSERT_EQ(db_iter->key().ToString(), "b");
|
||||
@@ -2680,11 +2623,12 @@ class DBIterWithMergeIterTest : public testing::Test {
|
||||
InternalIterator* merge_iter =
|
||||
NewMergingIterator(&icomp_, child_iters.data(), 2u);
|
||||
|
||||
db_iter_.reset(NewDBIterator(
|
||||
options_.max_sequential_skip_in_iterations = 3;
|
||||
db_iter_.reset(DBIter::NewIter(
|
||||
env_, ro_, ImmutableOptions(options_), MutableCFOptions(options_),
|
||||
BytewiseComparator(), merge_iter, nullptr /* version */,
|
||||
8 /* read data earlier than seqId 8 */,
|
||||
3 /* max iterators before reseek */, nullptr /* read_callback */));
|
||||
8 /* read data earlier than seqId 8 */, nullptr /* read_callback */,
|
||||
/*active_mem=*/nullptr));
|
||||
}
|
||||
|
||||
Env* env_;
|
||||
@@ -3120,11 +3064,10 @@ TEST_F(DBIteratorTest, SeekPrefixTombstones) {
|
||||
internal_iter->Finish();
|
||||
|
||||
ro.prefix_same_as_start = true;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
int skipped_keys = 0;
|
||||
|
||||
@@ -3157,11 +3100,11 @@ TEST_F(DBIteratorTest, SeekToFirstLowerBound) {
|
||||
Slice lower_bound(lower_bound_str);
|
||||
ro.iterate_lower_bound = &lower_bound;
|
||||
Options options;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
std::unique_ptr<Iterator> db_iter(
|
||||
DBIter::NewIter(env_, ro, ImmutableOptions(options),
|
||||
MutableCFOptions(options), BytewiseComparator(),
|
||||
internal_iter, nullptr /* version */, 10 /* sequence */,
|
||||
nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToFirst();
|
||||
if (i == kNumKeys + 1) {
|
||||
@@ -3197,11 +3140,10 @@ TEST_F(DBIteratorTest, PrevLowerBound) {
|
||||
Slice lower_bound(lower_bound_str);
|
||||
ro.iterate_lower_bound = &lower_bound;
|
||||
Options options;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekToLast();
|
||||
for (int i = kNumKeys; i >= kLowerBound; --i) {
|
||||
@@ -3226,11 +3168,10 @@ TEST_F(DBIteratorTest, SeekLessLowerBound) {
|
||||
Slice lower_bound(lower_bound_str);
|
||||
ro.iterate_lower_bound = &lower_bound;
|
||||
Options options;
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ro, ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
auto before_lower_bound_str = std::to_string(kLowerBound - 1);
|
||||
Slice before_lower_bound(lower_bound_str);
|
||||
@@ -3252,11 +3193,10 @@ TEST_F(DBIteratorTest, ReverseToForwardWithDisappearingKeys) {
|
||||
}
|
||||
internal_iter->Finish();
|
||||
|
||||
std::unique_ptr<Iterator> db_iter(NewDBIterator(
|
||||
std::unique_ptr<Iterator> db_iter(DBIter::NewIter(
|
||||
env_, ReadOptions(), ImmutableOptions(options), MutableCFOptions(options),
|
||||
BytewiseComparator(), internal_iter, nullptr /* version */,
|
||||
10 /* sequence */, options.max_sequential_skip_in_iterations,
|
||||
nullptr /* read_callback */));
|
||||
10 /* sequence */, nullptr /* read_callback */, /*active_mem=*/nullptr));
|
||||
|
||||
db_iter->SeekForPrev("a");
|
||||
ASSERT_TRUE(db_iter->Valid());
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/db_iter.h"
|
||||
@@ -3824,6 +3826,846 @@ TEST_F(DBIteratorTest, IteratorsConsistentViewExplicitSnapshot) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, MemtableOpsScanFlushTriggerWithSeek) {
|
||||
// Tests that option memtable_op_scan_flush_trigger works when the limit
|
||||
// is reached during a Seek() operation.
|
||||
const int kTrigger = 10;
|
||||
Random* r = Random::GetTLSInstance();
|
||||
|
||||
for (int trigger : {kTrigger, kTrigger + 1}) {
|
||||
for (bool delete_only : {false, true}) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_op_scan_flush_trigger = trigger;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Base data that will be covered by a consecutive sequence of tombstones.
|
||||
int kNumKeys = delete_only ? kTrigger : kTrigger / 2;
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
|
||||
if (delete_only) {
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(SingleDelete(Key(i)));
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(100)));
|
||||
}
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
}
|
||||
|
||||
SetPerfLevel(PerfLevel::kEnableCount);
|
||||
get_perf_context()->Reset();
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
|
||||
// Seek to the first key, this will scan through all the tombstones and
|
||||
// hidden puts
|
||||
iter->Seek(Key(0));
|
||||
ASSERT_FALSE(
|
||||
iter->Valid()); // All keys are deleted, so iterator is not valid
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kTrigger);
|
||||
|
||||
// Skipping kNumTrigger memtable entries in a single iterator operation
|
||||
// should mark the memtable for flush.
|
||||
//
|
||||
// At the end of a write, we check and update memtable to request a flush
|
||||
ASSERT_OK(Put(Key(11), "val"));
|
||||
// Before a write, we schedule memtables for flush if requested.
|
||||
ASSERT_OK(Put(Key(12), "val"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
|
||||
if (trigger <= kTrigger) {
|
||||
// Check if memtable was flushed due to scan trigger
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
uint64_t val = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
|
||||
ASSERT_EQ(0, val);
|
||||
} else {
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
uint64_t val = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
|
||||
ASSERT_EQ(kNumKeys, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, MemtableOpsScanFlushTriggerWithNext) {
|
||||
// Tests that option memtable_op_scan_flush_trigger works when the limit
|
||||
// is reached during a Next() operation, and not trigger a flush when
|
||||
// the limit is reached across multiple Next() operations.
|
||||
const int kTrigger = 10;
|
||||
Random* r = Random::GetTLSInstance();
|
||||
|
||||
for (int trigger : {kTrigger, kTrigger + 1}) {
|
||||
for (bool delete_only : {false, true}) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_op_scan_flush_trigger = trigger;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Base data that will be covered by a consecutive sequence of tombstones.
|
||||
int kNumKeys = delete_only ? kTrigger : kTrigger / 2;
|
||||
for (int i = 0; i <= kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(100)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
if (delete_only) {
|
||||
for (int i = 1; i <= kNumKeys; ++i) {
|
||||
ASSERT_OK(SingleDelete(Key(i)));
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; i <= kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(100)));
|
||||
}
|
||||
for (int i = 1; i <= kNumKeys; ++i) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
}
|
||||
|
||||
// Total number of tombstones and hidden puts scanned across multiple
|
||||
// Next() operations below will be kTrigger, and it should not trigger a
|
||||
// flush when the limit is kTrigger + 1.
|
||||
ASSERT_OK(Put(Key(kNumKeys + 1), "v1"));
|
||||
ASSERT_OK(Delete(Key(kNumKeys + 2)));
|
||||
ASSERT_OK(Put(Key(kNumKeys + 3), "v3"));
|
||||
|
||||
SetPerfLevel(PerfLevel::kEnableCount);
|
||||
get_perf_context()->Reset();
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
iter->Seek(Key(0));
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->value(), "val");
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(get_perf_context()->next_on_memtable_count, 0);
|
||||
iter->Next();
|
||||
// kTrigger tombstones and invisible puts and 1 for the visible put
|
||||
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kTrigger + 1);
|
||||
iter->Next();
|
||||
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kTrigger + 3);
|
||||
|
||||
// Skipping kNumTrigger memtable entries in a single iterator operation
|
||||
// should mark the memtable for flush.
|
||||
//
|
||||
// At the end of a write, we check and update memtable to request a flush
|
||||
ASSERT_OK(Put(Key(11), "val"));
|
||||
// Before a write, we schedule memtables for flush if requested.
|
||||
ASSERT_OK(Put(Key(12), "val"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
|
||||
if (trigger <= kTrigger) {
|
||||
// Check if memtable was flushed due to scan trigger
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
uint64_t val = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
|
||||
ASSERT_EQ(0, val);
|
||||
} else {
|
||||
uint64_t val = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty("rocksdb.num-deletes-active-mem-table", &val));
|
||||
ASSERT_EQ(kNumKeys + 1, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTrigger) {
|
||||
// Tests option memtable_avg_op_scan_flush_trigger with
|
||||
// long tombstone sequences.
|
||||
Random* r = Random::GetTLSInstance();
|
||||
|
||||
const int kAvgTrigger = 10;
|
||||
const int kMaxTrigger = 500;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_op_scan_flush_trigger = kMaxTrigger;
|
||||
options.memtable_avg_op_scan_flush_trigger = kAvgTrigger;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const int kNumKeys = 1000;
|
||||
// Base data that will be covered by a consecutive sequence of tombstones.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
// We issue slightly more deletions than kAvgTrigger between visible keys
|
||||
// to ensure avg skipped entries exceed kAvgTrigger.
|
||||
if (i % (kAvgTrigger + 2) != 0) {
|
||||
ASSERT_OK(SingleDelete(Key(i)));
|
||||
}
|
||||
}
|
||||
|
||||
// Each operation, except the first Seek, is expected to see kAvgTrigger + 1
|
||||
// tombstones (from the active memtable) before it finds the next visible key.
|
||||
SetPerfLevel(PerfLevel::kEnableCount);
|
||||
get_perf_context()->Reset();
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
iter->Seek(Key(1));
|
||||
ASSERT_EQ(get_perf_context()->next_on_memtable_count, kAvgTrigger + 1);
|
||||
iter.reset();
|
||||
// Should not flush since total entries skipped is below
|
||||
// memtable_op_scan_flush_trigger
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
|
||||
get_perf_context()->Reset();
|
||||
iter.reset(db_->NewIterator(ReadOptions()));
|
||||
int num_ops = 1;
|
||||
uint64_t num_skipped = 0;
|
||||
iter->Seek(Key(0));
|
||||
ASSERT_EQ(iter->key(), Key(0));
|
||||
uint64_t last_memtable_next_count =
|
||||
get_perf_context()->next_on_memtable_count;
|
||||
iter->Next();
|
||||
num_ops++;
|
||||
while (iter->Valid()) {
|
||||
ASSERT_OK(iter->status());
|
||||
uint64_t num_skipped_in_op =
|
||||
get_perf_context()->next_on_memtable_count - last_memtable_next_count;
|
||||
ASSERT_GE(num_skipped_in_op, kAvgTrigger + 1);
|
||||
last_memtable_next_count = get_perf_context()->next_on_memtable_count;
|
||||
num_skipped += num_skipped_in_op;
|
||||
iter->Next();
|
||||
num_ops++;
|
||||
}
|
||||
// During iterator destruction we mark memtable for flush
|
||||
iter.reset();
|
||||
|
||||
// avg trigger
|
||||
ASSERT_GE(num_skipped, kAvgTrigger * num_ops);
|
||||
// memtable_op_scan_flush_trigger
|
||||
ASSERT_GE(num_skipped, kMaxTrigger);
|
||||
// Average hidden entries scanned from memtable per operation is more than
|
||||
// kAvgTrigger and the total skipped is more than
|
||||
// memtable_op_scan_flush_trigger, the current memtable should be marked for
|
||||
// flush. The following two writes will trigger the flush.
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
// Before a write, we schedule memtables for flush if requested.
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTriggerByOverwrites) {
|
||||
// Tests option memtable_avg_op_scan_flush_trigger with overwrites to keys.
|
||||
Random* r = Random::GetTLSInstance();
|
||||
|
||||
const int kAvgTrigger = 25;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.memtable_op_scan_flush_trigger = 250;
|
||||
options.memtable_avg_op_scan_flush_trigger = kAvgTrigger;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const int kNumKeys = 100;
|
||||
// Base data that will be covered by a consecutive sequence of tombstones.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(6));
|
||||
|
||||
// One visible key every 10 keys.
|
||||
// Each non-visible user key has 3 non-visible entries in the active memtable.
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
if (i % 10 != 0) {
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(50)));
|
||||
ASSERT_OK(Put(Key(i), r->RandomString(50)));
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
}
|
||||
|
||||
SetPerfLevel(PerfLevel::kEnableCount);
|
||||
get_perf_context()->Reset();
|
||||
ReadOptions ro;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
iter->Seek(Key(1));
|
||||
ASSERT_GT(get_perf_context()->next_on_memtable_count, kAvgTrigger);
|
||||
// Re-seek to trigger check for flush trigger
|
||||
iter->Seek(Key(1));
|
||||
// Should not flush since total entries skipped is below
|
||||
// memtable_op_scan_flush_trigger
|
||||
ASSERT_FALSE(static_cast<ColumnFamilyHandleImpl*>(db_->DefaultColumnFamily())
|
||||
->cfd()
|
||||
->mem()
|
||||
->IsMarkedForFlush());
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
get_perf_context()->Reset();
|
||||
|
||||
int num_ops = 1;
|
||||
iter->Seek(Key(1));
|
||||
while (iter->Valid()) {
|
||||
num_ops++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_GT(get_perf_context()->next_on_memtable_count, num_ops * kAvgTrigger);
|
||||
|
||||
// Re-seek should check conditions for marking memtable for flush
|
||||
iter->Seek(Key(80));
|
||||
|
||||
// Average hidden entries scanned from memtable per operation is 2.
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
// Before a write, we schedule memtables for flush if requested.
|
||||
ASSERT_OK(Put(Key(0), "dummy write"));
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
|
||||
class DBMultiScanIteratorTest : public DBTestBase,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBMultiScanIteratorTest()
|
||||
: DBTestBase("db_multi_scan_iterator_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
// Param 0: ReadOptions::fill_cache
|
||||
INSTANTIATE_TEST_CASE_P(DBMultiScanIteratorTest, DBMultiScanIteratorTest,
|
||||
::testing::Bool());
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, BasicTest) {
|
||||
// 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(), "val" + ss.str()));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> key_ranges({"k03", "k10", "k25", "k50"});
|
||||
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 {
|
||||
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, 32);
|
||||
} 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) {
|
||||
// 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(), "val" + ss.str()));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> key_ranges(
|
||||
{"k03", "k10", "k25", "k50", "k75", "k90"});
|
||||
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]);
|
||||
scan_options.insert(key_ranges[4], key_ranges[5]);
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<MultiScan> 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(
|
||||
scan_options.GetScanRanges()[idx].range.start->ToString()),
|
||||
0);
|
||||
if (scan_options.GetScanRanges()[idx].range.limit) {
|
||||
ASSERT_LT(
|
||||
it.first.ToString().compare(
|
||||
scan_options.GetScanRanges()[idx].range.limit->ToString()),
|
||||
0);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
ASSERT_EQ(count, 97);
|
||||
} 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();
|
||||
scan_options = MultiScanArgs(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
scan_options.insert(key_ranges[4]);
|
||||
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(
|
||||
scan_options.GetScanRanges()[idx].range.start->ToString()),
|
||||
0);
|
||||
if (scan_options.GetScanRanges()[idx].range.limit) {
|
||||
ASSERT_LT(
|
||||
it.first.ToString().compare(
|
||||
scan_options.GetScanRanges()[idx].range.limit->ToString()),
|
||||
0);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
ASSERT_EQ(count, 147);
|
||||
} 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, RangeAcrossFiles) {
|
||||
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));
|
||||
std::vector<std::string> key_ranges({Key(10), Key(90)});
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
int i = 10;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_EQ(it.first.ToString(), Key(i));
|
||||
++i;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(i, 90);
|
||||
} catch (MultiScanException& ex) {
|
||||
// Make sure exception contains the status
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, FailureTest) {
|
||||
auto options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
// Create a file
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(2) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> key_ranges({"k04", "k06", "k12", "k14"});
|
||||
ReadOptions ro;
|
||||
Slice ub;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
ro.fill_cache = GetParam();
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
scan_options.max_prefetch_size = 4500;
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
|
||||
ASSERT_NE(iter, nullptr);
|
||||
iter->Prepare(scan_options);
|
||||
int count = 0;
|
||||
ub = key_ranges[1];
|
||||
iter->Seek(key_ranges[0]);
|
||||
while (iter->status().ok() && iter->Valid()) {
|
||||
ASSERT_GE(iter->key().compare(key_ranges[0]), 0);
|
||||
ASSERT_LT(iter->key().compare(key_ranges[1]), 0);
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status()) << iter->status().ToString();
|
||||
ASSERT_EQ(count, 2);
|
||||
|
||||
// Second seek should hit the max_prefetch_size limit
|
||||
ub = key_ranges[3];
|
||||
iter->Seek(key_ranges[2]);
|
||||
ASSERT_NOK(iter->status());
|
||||
iter.reset();
|
||||
|
||||
// Test the case of unexpected Seek key
|
||||
iter.reset(dbfull()->NewIterator(ro, cfh));
|
||||
ASSERT_NE(iter, nullptr);
|
||||
scan_options.max_prefetch_size = 0;
|
||||
iter->Prepare(scan_options);
|
||||
ub = key_ranges[3];
|
||||
iter->Seek(key_ranges[2]);
|
||||
ASSERT_NOK(iter->status());
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, OutOfL0FileRange) {
|
||||
// Test that prepare does not fail scan when a scan range
|
||||
// is outside of a L0 file's key range.
|
||||
auto options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
// Create a Lmax file
|
||||
// key01 ~ key99
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(2) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
// Create a L0 file
|
||||
// key00 ~ key09
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
std::stringstream ss;
|
||||
ss << std::setw(2) << std::setfill('0') << i;
|
||||
ASSERT_OK(Put("k" + ss.str(), rnd.RandomString(1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
|
||||
// The second range is outside of L0 file's key range
|
||||
std::vector<std::string> key_ranges({"k04", "k06", "k12", "k14"});
|
||||
ReadOptions ro;
|
||||
Slice ub;
|
||||
ro.iterate_upper_bound = &ub;
|
||||
ro.fill_cache = GetParam();
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<Iterator> iter(dbfull()->NewIterator(ro, cfh));
|
||||
ASSERT_NE(iter, nullptr);
|
||||
iter->Prepare(scan_options);
|
||||
int count = 0;
|
||||
ub = key_ranges[1];
|
||||
iter->Seek(key_ranges[0]);
|
||||
while (iter->status().ok() && iter->Valid()) {
|
||||
ASSERT_GE(iter->key().compare(key_ranges[0]), 0);
|
||||
ASSERT_LT(iter->key().compare(key_ranges[1]), 0);
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status()) << iter->status().ToString();
|
||||
ASSERT_EQ(count, 2);
|
||||
|
||||
ub = key_ranges[3];
|
||||
count = 0;
|
||||
iter->Seek(key_ranges[2]);
|
||||
while (iter->status().ok() && iter->Valid()) {
|
||||
ASSERT_GE(iter->key().compare(key_ranges[2]), 0);
|
||||
ASSERT_LT(iter->key().compare(key_ranges[3]), 0);
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status()) << iter->status().ToString();
|
||||
ASSERT_EQ(count, 2);
|
||||
}
|
||||
|
||||
TEST_P(DBMultiScanIteratorTest, RangeBetweenFiles) {
|
||||
auto options = CurrentOptions();
|
||||
options.target_file_size_base = 100 << 10; // 20KB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
auto rnd = Random::GetTLSInstance();
|
||||
// Write ~200KB data
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ASSERT_OK(Put(Key(i), rnd->RandomString(2 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(49));
|
||||
|
||||
// Test with a scan range that overlaps an entire file, with upper bound
|
||||
// between 2 files
|
||||
std::vector<LiveFileMetaData> file_meta;
|
||||
dbfull()->GetLiveFilesMetaData(&file_meta);
|
||||
ASSERT_EQ(file_meta.size(), 2);
|
||||
std::vector<std::string> key_ranges(4);
|
||||
key_ranges[0] = file_meta[0].smallestkey;
|
||||
key_ranges[1] = file_meta[0].largestkey + "0";
|
||||
key_ranges[2] = file_meta[1].smallestkey + "0";
|
||||
key_ranges[3] = file_meta[1].largestkey;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[0]);
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
// Make sure exception contains the status
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
iter.reset();
|
||||
|
||||
// Test multiscan with a range entirely between adjacent files
|
||||
key_ranges[0] = file_meta[0].largestkey + "0";
|
||||
key_ranges[1] = file_meta[0].largestkey + "1";
|
||||
key_ranges[2] = file_meta[1].smallestkey + "0";
|
||||
key_ranges[3] = file_meta[1].largestkey;
|
||||
(*scan_options).clear();
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[0]);
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
// Make sure exception contains the status
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
// This test case tests multiscan in the presence of fragmented range
|
||||
// tombstones in the LSM.
|
||||
TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
|
||||
auto options = CurrentOptions();
|
||||
// Compaction may create files 2x the target_file_size_base,
|
||||
// so set this to 50KB so we atleast end up with 2 files of
|
||||
// 100KB
|
||||
options.target_file_size_base = 50 << 10; // 50KB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Setup the LSM as follows -
|
||||
// 1. Ingest a file with 100 keys
|
||||
// 2. Ingest a file with one overlapping key
|
||||
// 3. Do a Put and flush a file to L0 with one overlapping key
|
||||
// 4. Ingest a standalone delete range file that covers the full key space
|
||||
// and a file with the same 100 keys with new values. This will ingest
|
||||
// into L0 due to the presence of an existing file in L0
|
||||
// The final LSM will have an SST in Lmax with 100 keys, and 2 SST files
|
||||
// in Lmax-1 with half the keys each and completely overlapping delete ranges
|
||||
std::unordered_map<std::string, std::string> kvs;
|
||||
auto rnd = Random::GetTLSInstance();
|
||||
auto create_ingestion_data_file_and_update_key_value =
|
||||
[&](const std::string& filename, int start_key, int end_key) {
|
||||
std::unique_ptr<SstFileWriter> writer;
|
||||
writer.reset(new SstFileWriter(EnvOptions(), options));
|
||||
ASSERT_OK(writer->Open(filename));
|
||||
for (int i = start_key; i < end_key; ++i) {
|
||||
auto kiter = kvs.find(Key(i));
|
||||
if (kiter != kvs.end()) {
|
||||
kvs.erase(kiter);
|
||||
}
|
||||
auto res =
|
||||
kvs.emplace(std::make_pair(Key(i), rnd->RandomString(2 << 10)));
|
||||
ASSERT_OK(writer->Put(res.first->first, res.first->second));
|
||||
}
|
||||
ASSERT_OK(writer->Finish());
|
||||
writer.reset();
|
||||
};
|
||||
|
||||
CreateColumnFamilies({"new_cf"}, options);
|
||||
std::string ingest_file = dbname_ + "test.sst";
|
||||
// Write ~200KB data
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_0", 0, 100);
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_1", 50, 51);
|
||||
ColumnFamilyHandle* cfh = handles_[0];
|
||||
IngestExternalFileOptions ifo;
|
||||
Status s = dbfull()->IngestExternalFile(
|
||||
cfh, {ingest_file + "_0", ingest_file + "_1"}, ifo);
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(Put(0, Key(50), rnd->RandomString(2 << 10)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
{
|
||||
std::unique_ptr<SstFileWriter> writer;
|
||||
writer.reset(new SstFileWriter(EnvOptions(), options));
|
||||
ASSERT_OK(writer->Open(ingest_file + "_2"));
|
||||
ASSERT_OK(writer->DeleteRange("a", "z"));
|
||||
ASSERT_OK(writer->Finish());
|
||||
writer.reset();
|
||||
}
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_3", 0, 100);
|
||||
s = dbfull()->IngestExternalFile(
|
||||
cfh, {ingest_file + "_2", ingest_file + "_3"}, ifo);
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -312,12 +312,12 @@ TEST_P(DbKvChecksumTest, WriteToWALCorrupted) {
|
||||
// Corrupted write batch leads to read-only mode, so we have to
|
||||
// reopen for every attempt.
|
||||
Reopen(options);
|
||||
auto log_size_pre_write = dbfull()->TEST_total_log_size();
|
||||
auto log_size_pre_write = dbfull()->TEST_wals_total_size();
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption());
|
||||
// Confirm that nothing was written to WAL
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_wals_total_size());
|
||||
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
@@ -350,12 +350,12 @@ TEST_P(DbKvChecksumTest, WriteToWALWithColumnFamilyCorrupted) {
|
||||
// Corrupted write batch leads to read-only mode, so we have to
|
||||
// reopen for every attempt.
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "pikachu"}, options);
|
||||
auto log_size_pre_write = dbfull()->TEST_total_log_size();
|
||||
auto log_size_pre_write = dbfull()->TEST_wals_total_size();
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_TRUE(ExecuteWrite(nullptr /* cf_handle */).IsCorruption());
|
||||
// Confirm that nothing was written to WAL
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_wals_total_size());
|
||||
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
@@ -487,7 +487,7 @@ TEST_P(DbKvChecksumTestMergedBatch, WriteToWALCorrupted) {
|
||||
// Reopen DB since it failed WAL write which lead to read-only mode
|
||||
Reopen(options);
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto log_size_pre_write = dbfull()->TEST_total_log_size();
|
||||
auto log_size_pre_write = dbfull()->TEST_wals_total_size();
|
||||
leader_batch_and_status =
|
||||
GetWriteBatch(GetCFHandleToUse(nullptr, op_type1_),
|
||||
8 /* protection_bytes_per_key */, op_type1_);
|
||||
@@ -499,7 +499,7 @@ TEST_P(DbKvChecksumTestMergedBatch, WriteToWALCorrupted) {
|
||||
SyncPoint::GetInstance()->ClearCallBack("WriteThread::JoinBatchGroup:Wait");
|
||||
ASSERT_EQ(1, leader_count);
|
||||
// Nothing should have been written to WAL
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_wals_total_size());
|
||||
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
|
||||
|
||||
corrupt_byte_offset++;
|
||||
@@ -599,7 +599,7 @@ TEST_P(DbKvChecksumTestMergedBatch, WriteToWALWithColumnFamilyCorrupted) {
|
||||
// Reopen DB since it failed WAL write which lead to read-only mode
|
||||
ReopenWithColumnFamilies({kDefaultColumnFamilyName, "ramen"}, options);
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto log_size_pre_write = dbfull()->TEST_total_log_size();
|
||||
auto log_size_pre_write = dbfull()->TEST_wals_total_size();
|
||||
leader_batch_and_status =
|
||||
GetWriteBatch(GetCFHandleToUse(handles_[1], op_type1_),
|
||||
8 /* protection_bytes_per_key */, op_type1_);
|
||||
@@ -612,7 +612,7 @@ TEST_P(DbKvChecksumTestMergedBatch, WriteToWALWithColumnFamilyCorrupted) {
|
||||
|
||||
ASSERT_EQ(1, leader_count);
|
||||
// Nothing should have been written to WAL
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_total_log_size());
|
||||
ASSERT_EQ(log_size_pre_write, dbfull()->TEST_wals_total_size());
|
||||
ASSERT_TRUE(dbfull()->TEST_GetBGError().IsCorruption());
|
||||
|
||||
corrupt_byte_offset++;
|
||||
|
||||
@@ -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
|
||||
@@ -424,6 +553,96 @@ TEST_F(DBMemTableTest, IntegrityChecks) {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBMemTableTest, VectorConcurrentInsert) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.create_missing_column_families = true;
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
options.memtable_factory.reset(new VectorRepFactory());
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"cf1"}, options);
|
||||
|
||||
// Multi-threaded writes
|
||||
{
|
||||
WriteOptions write_options;
|
||||
std::vector<port::Thread> threads;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
threads.emplace_back([&, i]() {
|
||||
int start = i * 100;
|
||||
int end = start + 100;
|
||||
WriteBatch batch;
|
||||
for (int j = start; j < end; ++j) {
|
||||
ASSERT_OK(
|
||||
batch.Put(handles_[0], Key(j), "value" + std::to_string(j)));
|
||||
}
|
||||
ASSERT_OK(db_->Write(write_options, &batch));
|
||||
});
|
||||
}
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(ReadOptions(), handles_[0]));
|
||||
iter->SeekToFirst();
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_EQ(iter->key().ToString(), Key(i));
|
||||
ASSERT_EQ(iter->value().ToString(), "value" + std::to_string(i));
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
// Multi-threaded writes, multi CF
|
||||
{
|
||||
WriteOptions write_options;
|
||||
std::vector<port::Thread> threads;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
threads.emplace_back([&, i]() {
|
||||
int start = i * 100;
|
||||
int end = start + 100;
|
||||
WriteBatch batch;
|
||||
for (int j = start; j < end; ++j) {
|
||||
ASSERT_OK(batch.Put(handles_[0], Key(j), "CF0" + std::to_string(j)));
|
||||
ASSERT_OK(batch.Put(handles_[1], Key(j), "CF1" + std::to_string(j)));
|
||||
}
|
||||
ASSERT_OK(db_->Write(write_options, &batch));
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
t.join();
|
||||
}
|
||||
|
||||
std::unique_ptr<Iterator> iter0(
|
||||
db_->NewIterator(ReadOptions(), handles_[0]));
|
||||
std::unique_ptr<Iterator> iter1(
|
||||
db_->NewIterator(ReadOptions(), handles_[1]));
|
||||
iter0->SeekToFirst();
|
||||
iter1->SeekToFirst();
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
ASSERT_TRUE(iter0->Valid());
|
||||
ASSERT_EQ(iter0->key().ToString(), Key(i));
|
||||
ASSERT_EQ(iter0->value().ToString(), "CF0" + std::to_string(i));
|
||||
iter0->Next();
|
||||
|
||||
ASSERT_TRUE(iter1->Valid());
|
||||
ASSERT_EQ(iter1->key().ToString(), Key(i));
|
||||
ASSERT_EQ(iter1->value().ToString(), "CF1" + std::to_string(i));
|
||||
iter1->Next();
|
||||
}
|
||||
ASSERT_FALSE(iter0->Valid());
|
||||
ASSERT_OK(iter0->status());
|
||||
ASSERT_FALSE(iter1->Valid());
|
||||
ASSERT_OK(iter1->status());
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+52
-22
@@ -322,31 +322,26 @@ TEST_F(DBOptionsTest, SetWithCustomMemTableFactory) {
|
||||
}
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
// Try with fail_if_options_file_error=false/true to update the options
|
||||
for (bool on_error : {false, true}) {
|
||||
options.fail_if_options_file_error = on_error;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = false;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = false;
|
||||
|
||||
options.memtable_factory.reset(new DummySkipListFactory());
|
||||
Reopen(options);
|
||||
options.memtable_factory.reset(new DummySkipListFactory());
|
||||
Reopen(options);
|
||||
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(cfh, {{"disable_auto_compactions", "true"}}));
|
||||
ColumnFamilyDescriptor cfd;
|
||||
ASSERT_OK(cfh->GetDescriptor(&cfd));
|
||||
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
|
||||
DummySkipListFactory::kClassName());
|
||||
ColumnFamilyHandle* test = nullptr;
|
||||
ASSERT_OK(dbfull()->CreateColumnFamily(options, "test", &test));
|
||||
ASSERT_OK(test->GetDescriptor(&cfd));
|
||||
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
|
||||
DummySkipListFactory::kClassName());
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
ASSERT_OK(dbfull()->SetOptions(cfh, {{"disable_auto_compactions", "true"}}));
|
||||
ColumnFamilyDescriptor cfd;
|
||||
ASSERT_OK(cfh->GetDescriptor(&cfd));
|
||||
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
|
||||
DummySkipListFactory::kClassName());
|
||||
ColumnFamilyHandle* test = nullptr;
|
||||
ASSERT_OK(dbfull()->CreateColumnFamily(options, "test", &test));
|
||||
ASSERT_OK(test->GetDescriptor(&cfd));
|
||||
ASSERT_STREQ(cfd.options.memtable_factory->Name(),
|
||||
DummySkipListFactory::kClassName());
|
||||
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(test));
|
||||
delete test;
|
||||
}
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(test));
|
||||
delete test;
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetBytesPerSync) {
|
||||
@@ -437,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);
|
||||
|
||||
@@ -377,6 +377,8 @@ TEST_F(DBPropertiesTest, AggregatedTableProperties) {
|
||||
NewBloomFilterPolicy(kBloomBitsPerKey, false));
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
// The checks assume kTableCount number of files
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -567,7 +569,7 @@ TEST_F(DBPropertiesTest, AggregatedTablePropertiesAtLevel) {
|
||||
options.target_file_size_base = 8192;
|
||||
options.max_bytes_for_level_base = 10000;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
// This ensures there no compaction happening when we call GetProperty().
|
||||
// The checks assume kTableCount number of files
|
||||
options.disable_auto_compactions = true;
|
||||
options.merge_operator.reset(new TestPutOperator());
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Loaded 100 of 457 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user