mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb4497729c | ||
|
|
75623f6a7a | ||
|
|
812b12bc78 | ||
|
|
fba76b2ab8 | ||
|
|
24efab2f04 | ||
|
|
c8746289ba | ||
|
|
6507b9ced8 | ||
|
|
d3713c0fe3 | ||
|
|
d18c2906fa | ||
|
|
f972d77133 | ||
|
|
9775409298 | ||
|
|
4e7fe99741 | ||
|
|
4b0558b948 | ||
|
|
a74934f0f5 | ||
|
|
b4a3612256 | ||
|
|
c44a283847 | ||
|
|
12b6696e7f | ||
|
|
10985ceb00 | ||
|
|
cd6491d0a4 | ||
|
|
c411586f1b | ||
|
|
09074d7fb7 | ||
|
|
f9ef31a08e | ||
|
|
17bcdd9bce | ||
|
|
28d9cc6484 | ||
|
|
66236860f3 | ||
|
|
6302542b9c | ||
|
|
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 |
No files matched your search
@@ -4,8 +4,8 @@ runs:
|
||||
steps:
|
||||
- name: Install Maven
|
||||
run: |
|
||||
wget --no-check-certificate https://dlcdn.apache.org/maven/maven-3/3.9.10/binaries/apache-maven-3.9.10-bin.tar.gz
|
||||
tar zxf apache-maven-3.9.10-bin.tar.gz
|
||||
echo "export M2_HOME=$(pwd)/apache-maven-3.9.10" >> $GITHUB_ENV
|
||||
echo "$(pwd)/apache-maven-3.9.10/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
|
||||
@@ -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"
|
||||
+60
-153
@@ -66,7 +66,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 16-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: zjay437/rocksdb:0.6
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:24.0
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -97,11 +97,8 @@ 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"
|
||||
@@ -109,32 +106,13 @@ jobs:
|
||||
- uses: "./.github/actions/build-folly"
|
||||
- run: "(mkdir build && cd build && cmake -DUSE_FOLLY=1 -DWITH_GFLAGS=1 -DROCKSDB_BUILD_SHARED=0 .. && make V=1 -j20 && ctest -j20)"
|
||||
- uses: "./.github/actions/post-steps"
|
||||
build-linux-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"
|
||||
@@ -147,27 +125,21 @@ 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"
|
||||
@@ -180,7 +152,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
|
||||
@@ -192,13 +164,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:
|
||||
@@ -206,141 +178,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
|
||||
@@ -368,7 +281,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
|
||||
@@ -376,53 +289,41 @@ 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:
|
||||
@@ -469,11 +370,11 @@ jobs:
|
||||
- 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-2022
|
||||
env:
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_PORTABLE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
@@ -484,11 +385,13 @@ jobs:
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:centos7_x64-be
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
# The docker image is intentionally based on an OS that has an older GLIBC version.
|
||||
# That GLIBC is incompatibile with GitHub's actions/checkout. Thus we implement a manual checkout step.
|
||||
# NOTE: replaced evolvedbinary/rocksjava:centos7_x64-be with ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
# until a more appropriate docker image with C++20 support is made.
|
||||
- name: Checkout
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -505,18 +408,21 @@ jobs:
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- name: Test RocksDBJava
|
||||
run: scl enable devtoolset-7 'make V=1 J=8 -j8 jtest'
|
||||
# NOTE: post-steps skipped because of compatibility issues with docker image
|
||||
# NOTE: replaced scl enable devtoolset-7 'make V=1 J=8 -j8 jtest'
|
||||
run: make V=1 J=8 -j8 jtest
|
||||
# post-steps skipped because of compatibility issues with docker image
|
||||
build-linux-java-static:
|
||||
if: ${{ github.repository_owner == 'facebook' }}
|
||||
runs-on:
|
||||
labels: 4-core-ubuntu
|
||||
container:
|
||||
image: evolvedbinary/rocksjava:centos7_x64-be
|
||||
image: ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
options: --shm-size=16gb
|
||||
steps:
|
||||
# The docker image is intentionally based on an OS that has an older GLIBC version.
|
||||
# That GLIBC is incompatibile with GitHub's actions/checkout. Thus we implement a manual checkout step.
|
||||
# NOTE: replaced evolvedbinary/rocksjava:centos7_x64-be with ghcr.io/facebook/rocksdb_ubuntu:22.1
|
||||
# until a more appropriate docker image with C++20 support is made.
|
||||
- name: Checkout
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -533,8 +439,9 @@ 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
|
||||
|
||||
@@ -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,7 +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_skip_compressor.cc",
|
||||
"util/auto_tune_compressor.cc",
|
||||
"util/build_version.cc",
|
||||
"util/cleanable.cc",
|
||||
"util/coding.cc",
|
||||
@@ -417,16 +419,18 @@ 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_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",
|
||||
@@ -448,6 +452,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)
|
||||
@@ -5193,6 +5199,12 @@ cpp_unittest_wrapper(name="inlineskiplist_test",
|
||||
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"],
|
||||
@@ -5373,6 +5385,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"],
|
||||
|
||||
+12
-4
@@ -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)
|
||||
@@ -314,7 +314,6 @@ endif()
|
||||
|
||||
# Check if -latomic is required or not
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++17")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
std::atomic<uint64_t> x(0);
|
||||
@@ -721,6 +720,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 +746,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,7 +875,7 @@ set(SOURCES
|
||||
trace_replay/trace_record.cc
|
||||
trace_replay/trace_replay.cc
|
||||
util/async_file_reader.cc
|
||||
util/auto_skip_compressor.cc
|
||||
util/auto_tune_compressor.cc
|
||||
util/cleanable.cc
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
@@ -1502,6 +1503,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
|
||||
@@ -1612,6 +1614,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)
|
||||
|
||||
+89
-4
@@ -1,15 +1,100 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
|
||||
## 10.4.2 (07/09/2025)
|
||||
### Bug Fixes
|
||||
* Fix a race condition between concurrent DB::Open sharing the same SstFileManager instance.
|
||||
## 10.7.6 (10/23/2025)
|
||||
* Fix a bug when del range and data are in same file for multi-scan
|
||||
|
||||
## 10.7.5 (10/20/2025)
|
||||
### Bug Fixes
|
||||
* Fix a bug in Page unpinning in MultiScan
|
||||
|
||||
## 10.7.4 (10/14/2025)
|
||||
### Public API Changes
|
||||
* 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
|
||||
|
||||
## 10.7.3 (10/06/2025)
|
||||
### Bug Fixes
|
||||
* Fix a few bugs in MultiScan
|
||||
|
||||
## 10.7.2 (09/30/2025)
|
||||
### Bug Fixes
|
||||
* Fix incorrect MultiScan seek error status due to bugs in handling range limit falling between adjacent SST files key range.
|
||||
|
||||
### Performance Improvements
|
||||
* Fixed a performance regression in LZ4 compression that started in version 10.6.0
|
||||
|
||||
## 10.7.0 (09/24/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_veirfy_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.
|
||||
|
||||
## 10.4.1 (07/01/2025)
|
||||
### 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.
|
||||
* Allow UDIs with a non BytewiseComparator
|
||||
|
||||
### Bug Fixes
|
||||
* Reported numbers for compaction and flush CPU usage now include time spent by parallel compression worker threads. This now means compaction/flush CPU usage could exceed the wall clock time.
|
||||
* Fix a race condition in FIFO size-based compaction where concurrent threads could select the same non-L0 file, causing assertion failures in debug builds or "Cannot delete table file from LSM tree" errors in release builds.
|
||||
* Fix a bug in RocksDB MultiScan with UDI when one of the scan ranges is determined to be empty by the UDI, which causes incorrect results.
|
||||
|
||||
### Performance Improvements
|
||||
* Add a new table property "rocksdb.key.smallest.seqno" which records the smallest sequence number of all keys in file. It makes ingesting DB generated files faster by
|
||||
avoiding scanning the whole file to find the smallest sequence number.
|
||||
* Add a new experimental PerKeyPointLockManager to improve efficiency under high lock contention. PointLockManager was not efficient when there is high write contention on same key, as it uses a single conditional variable per lock stripe. PerKeyPointLockManager uses per thread conditional variable supporting fifo order. Although this is an experimental feature. By default, it is disabled. A new boolean flag TransactionDBOptions::use_per_key_point_lock_mgr is added to optionally enable it. Search the flag in code for more info.
|
||||
Together, a new configuration TransactionOptions::deadlock_timeout_us is added, which allows the transaction to wait for a short period before perform deadlock detection. When the workload has low lock contention, the deadlock_timeout_us can be configured to be slightly higher than average transaction execution time, so that transaction would likely be able to take the lock before deadlock detection is performed when it is waiting for a lock. This allows transaction to reduce CPU cost on performing deadlock detection, which could be expensive in CPU time. When the workload has high lock contention, the deadlock_timeout_us can be configured to 0, so that transaction would perform deadlock detection immediately. By default the value is 0 to keep the behavior same as before.
|
||||
* Majorly improved CPU efficiency and scalability of parallel compression (`CompressionOptions::parallel_threads` > 1), though this efficiency improvement makes parallel compression currently incompatible with UserDefinedIndex and with old setting of `decouple_partitioned_filters=false`. Parallel compression is now considered a production-ready feature. Maximum performance is available with `-DROCKSDB_USE_STD_SEMAPHORES` at compile time, but this is not currently recommended because of reported bugs in implementations of `std::counting_semaphore`/`binary_semaphore`.
|
||||
|
||||
## 10.6.0 (08/22/2025)
|
||||
### New Features
|
||||
* Introduce column family option `cf_allow_ingest_behind`. This option aims to replace `DBOptions::allow_ingest_behind` to enable ingest behind at the per-CF level. `DBOptions::allow_ingest_behind` is deprecated.
|
||||
* Introduce `MultiScanArgs::io_coalesce_threshold` to allow a configurable IO coalescing threshold.
|
||||
|
||||
### Public API Changes
|
||||
* `IngestExternalFileOptions::allow_db_generated_files` now allows files ingestion of any DB generated SST file, instead of only the ones with all keys having sequence number 0.
|
||||
* `decouple_partitioned_filters = true` is now the default in BlockBasedTableOptions.
|
||||
* GetTtl() API is now available in TTL DB
|
||||
* Minimum supported version of LZ4 library is now 1.7.0 (r129 from 2015)
|
||||
* Some changes to experimental Compressor and CompressionManager APIs
|
||||
* A new Filesystem::SyncFile function is added for syncing a file that was already written, such as on file ingestion. The default implementation matches previous RocksDB behavior: re-open the file for read-write, sync it, and close it. We recommend overriding for FileSystems that do not require syncing for crash recovery or do not handle (well) re-opening for writes.
|
||||
|
||||
### Behavior Changes
|
||||
* When `allow_ingest_behind` is enabled, compaction will no longer drop tombstones based on the absence of underlying data. Tombstones will be preserved to apply to ingested files.
|
||||
|
||||
### Bug Fixes
|
||||
* Files in dropped column family won't be returned to the caller upon successful, offline MANIFEST iteration in `GetFileChecksumsFromCurrentManifest`.
|
||||
* Fix a bug in MultiScan that causes it to fall back to a normal scan when dictionary compression is enabled.
|
||||
* Fix a crash in iterator Prepare() when fill_cache=false
|
||||
* Fix a bug in MultiScan where incorrect results can be returned when a Scan's range is across multiple files.
|
||||
* Fixed a bug in remote compaction that may mistakenly delete live SST file(s) during the cleanup phase when no keys survive the compaction (all expired)
|
||||
* Allow a user defined index to be configured from a string.
|
||||
* Make the User Defined Index interface consistently use the user key format, fixing the previous mixed usage of internal and user key.
|
||||
|
||||
### Performance Improvements
|
||||
* Small improvement to CPU efficiency of compression using built-in algorithms, and a dramatic efficiency improvement for LZ4HC, based on reusing data structures between invocations.
|
||||
|
||||
## 10.5.0 (07/18/2025)
|
||||
### Public API Changes
|
||||
* DB option skip_checking_sst_file_sizes_on_db_open is deprecated, in favor of validating file size in parallel in a thread pool, when db is opened. When DB is opened, with paranoid check enabled, a file with the wrong size would fail the DB open. With paranoid check disabled, the DB open would succeed, the column family with the corrupted file would not be read or write, while the other healthy column families could be read and write normally. When max_open_files option is not set to -1, only a subset of the files will be opened and checked. The rest of the files will be opened and checked when they are accessed.
|
||||
|
||||
### Behavior Changes
|
||||
* PessimisticTransaction::GetWaitingTxns now returns waiting transaction information even if the current transaction has timed out. This allows the information to be surfaced to users for debugging purposes once it is known that the timeout has occured.
|
||||
* 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
|
||||
|
||||
+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)
|
||||
@@ -1878,6 +1883,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)
|
||||
|
||||
@@ -2037,6 +2045,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
|
||||
@@ -2245,7 +2256,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:
|
||||
@@ -2492,7 +2503,7 @@ checkout_folly:
|
||||
fi
|
||||
@# Pin to a particular version for public CI, so that PR authors don't
|
||||
@# need to worry about folly breaking our integration. Update periodically
|
||||
cd third-party/folly && git reset --hard d17bf897cb5bbf8f07b122a614e8cffdc38edcde
|
||||
cd third-party/folly && git reset --hard e95383b7c8b5b1e46cf47acf2f317d54f93c8268
|
||||
@# Apparently missing include
|
||||
perl -pi -e 's/(#include <atomic>)/$$1\n#include <cstring>/' third-party/folly/folly/lang/Exception.h
|
||||
@# Warning-as-error on memcpy
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
+180
-144
@@ -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)
|
||||
@@ -40,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;
|
||||
@@ -62,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();
|
||||
const char* limit = ptr->get() + handle_value_charge;
|
||||
data_ptr =
|
||||
GetVarint32Ptr(data_ptr, limit, static_cast<uint32_t*>(&type_32));
|
||||
type = static_cast<CompressionType>(type_32);
|
||||
data_ptr =
|
||||
GetVarint32Ptr(data_ptr, limit, static_cast<uint32_t*>(&source_32));
|
||||
source = static_cast<CacheTier>(source_32);
|
||||
uint64_t data_size = 0;
|
||||
data_ptr =
|
||||
GetVarint64Ptr(data_ptr, limit, 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 {
|
||||
// TODO: can we work some magic with create_cb, which might be based on
|
||||
// custom compression, to decompress without an extra copy in create_cb?
|
||||
if (type != kNoCompression) {
|
||||
// TODO: can we do something to avoid yet another allocation?
|
||||
Decompressor::Args args;
|
||||
args.compressed_data = Slice(data_ptr, handle_value_charge);
|
||||
args.compression_type = cache_options_.compression_type;
|
||||
s = decompressor_->ExtractUncompressedSize(args);
|
||||
assert(s.ok());
|
||||
args.compressed_data = saved;
|
||||
args.compression_type = type;
|
||||
Status s = decompressor_->ExtractUncompressedSize(args);
|
||||
assert(s.ok()); // in-memory data
|
||||
if (s.ok()) {
|
||||
auto uncompressed = std::make_unique<char[]>(args.uncompressed_size);
|
||||
uncompressed = std::make_unique<char[]>(args.uncompressed_size);
|
||||
s = decompressor_->DecompressBlock(args, uncompressed.get());
|
||||
assert(s.ok());
|
||||
if (s.ok()) {
|
||||
s = helper->create_cb(
|
||||
Slice(uncompressed.get(), args.uncompressed_size), kNoCompression,
|
||||
CacheTier::kVolatileTier, create_context, allocator, &value,
|
||||
&charge);
|
||||
}
|
||||
assert(s.ok()); // in-memory data
|
||||
}
|
||||
if (!s.ok()) {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
|
||||
return nullptr;
|
||||
}
|
||||
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;
|
||||
@@ -148,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;
|
||||
}
|
||||
@@ -171,85 +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);
|
||||
assert(source == CacheTier::kVolatileCompressedTier);
|
||||
|
||||
CompressionType to_type = kNoCompression;
|
||||
s = compressor_->CompressBlock(val, &compressed_val, &to_type,
|
||||
// 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;
|
||||
}
|
||||
// TODO: allow values not compressed when there's no size savings?
|
||||
assert(to_type == cache_options_.compression_type);
|
||||
if (to_type != cache_options_.compression_type) {
|
||||
return Status::Corruption("Failed to compress value.");
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -271,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();
|
||||
}
|
||||
|
||||
@@ -291,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();
|
||||
}
|
||||
|
||||
@@ -321,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();
|
||||
@@ -344,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;
|
||||
@@ -367,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(
|
||||
@@ -402,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;
|
||||
}
|
||||
@@ -422,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
+5
-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);
|
||||
|
||||
@@ -149,7 +143,7 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
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
+6
-10
@@ -121,16 +121,13 @@ CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
|
||||
assert(s.ok());
|
||||
assert(placeholder_usage_ == 0);
|
||||
assert(reserved_usage_ == 0);
|
||||
bool pri_cache_res_mismatch =
|
||||
pri_cache_res_->GetTotalMemoryUsed() != sec_capacity;
|
||||
if (pri_cache_res_mismatch) {
|
||||
fprintf(stderr,
|
||||
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_);
|
||||
assert(pri_cache_res_mismatch);
|
||||
}
|
||||
}
|
||||
#endif // NDEBUG
|
||||
@@ -489,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()) {
|
||||
@@ -589,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;
|
||||
@@ -613,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(
|
||||
|
||||
@@ -98,7 +98,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
|
||||
bool PrepareValue() override { return db_iter_->PrepareValue(); }
|
||||
|
||||
void Prepare(const std::vector<ScanOptions>& scan_opts) override {
|
||||
void Prepare(const MultiScanArgs& scan_opts) override {
|
||||
db_iter_->Prepare(scan_opts);
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -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,
|
||||
@@ -343,6 +342,9 @@ Status BuildTable(
|
||||
if (s.ok() && !empty) {
|
||||
if (flush_stats) {
|
||||
flush_stats->bytes_written_pre_comp = builder->PreCompressionSize();
|
||||
// Add worker CPU micros here. Caller needs to add CPU micros from
|
||||
// calling thread.
|
||||
flush_stats->cpu_micros += builder->GetWorkerCPUMicros();
|
||||
}
|
||||
uint64_t file_size = builder->FileSize();
|
||||
meta->fd.file_size = file_size;
|
||||
|
||||
@@ -82,6 +82,7 @@ using ROCKSDB_NAMESPACE::DbPath;
|
||||
using ROCKSDB_NAMESPACE::Env;
|
||||
using ROCKSDB_NAMESPACE::EnvOptions;
|
||||
using ROCKSDB_NAMESPACE::EventListener;
|
||||
using ROCKSDB_NAMESPACE::ExportImportFilesMetaData;
|
||||
using ROCKSDB_NAMESPACE::ExternalFileIngestionInfo;
|
||||
using ROCKSDB_NAMESPACE::FileLock;
|
||||
using ROCKSDB_NAMESPACE::FilterPolicy;
|
||||
@@ -89,6 +90,7 @@ using ROCKSDB_NAMESPACE::FlushJobInfo;
|
||||
using ROCKSDB_NAMESPACE::FlushOptions;
|
||||
using ROCKSDB_NAMESPACE::HistogramData;
|
||||
using ROCKSDB_NAMESPACE::HyperClockCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::ImportColumnFamilyOptions;
|
||||
using ROCKSDB_NAMESPACE::InfoLogLevel;
|
||||
using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
|
||||
using ROCKSDB_NAMESPACE::Iterator;
|
||||
@@ -245,6 +247,9 @@ struct rocksdb_write_buffer_manager_t {
|
||||
struct rocksdb_sst_file_manager_t {
|
||||
std::shared_ptr<SstFileManager> rep;
|
||||
};
|
||||
struct rocksdb_livefile_t {
|
||||
LiveFileMetaData rep;
|
||||
};
|
||||
struct rocksdb_livefiles_t {
|
||||
std::vector<LiveFileMetaData> rep;
|
||||
};
|
||||
@@ -255,6 +260,12 @@ struct rocksdb_column_family_handle_t {
|
||||
struct rocksdb_column_family_metadata_t {
|
||||
ColumnFamilyMetaData rep;
|
||||
};
|
||||
struct rocksdb_export_import_files_metadata_t {
|
||||
ExportImportFilesMetaData* rep;
|
||||
};
|
||||
struct rocksdb_import_column_family_options_t {
|
||||
ImportColumnFamilyOptions rep;
|
||||
};
|
||||
struct rocksdb_level_metadata_t {
|
||||
const LevelMetaData* rep;
|
||||
};
|
||||
@@ -925,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;
|
||||
@@ -943,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;
|
||||
@@ -1186,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);
|
||||
@@ -3061,6 +3112,10 @@ uint64_t rocksdb_flushjobinfo_smallest_seqno(
|
||||
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();
|
||||
@@ -3192,6 +3247,11 @@ int rocksdb_subcompactionjobinfo_output_level(
|
||||
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(
|
||||
@@ -3626,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;
|
||||
}
|
||||
@@ -4733,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);
|
||||
@@ -6182,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());
|
||||
}
|
||||
@@ -6195,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;
|
||||
}
|
||||
@@ -6215,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;
|
||||
}
|
||||
@@ -6225,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,
|
||||
@@ -6375,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() {
|
||||
@@ -6406,6 +6621,11 @@ void rocksdb_transactiondb_options_set_default_lock_timeout(
|
||||
opt->rep.default_lock_timeout = default_lock_timeout;
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_options_set_use_per_key_point_lock_mgr(
|
||||
rocksdb_transactiondb_options_t* opt, int use_per_key_point_lock_mgr) {
|
||||
opt->rep.use_per_key_point_lock_mgr = use_per_key_point_lock_mgr;
|
||||
}
|
||||
|
||||
rocksdb_transaction_options_t* rocksdb_transaction_options_create() {
|
||||
return new rocksdb_transaction_options_t;
|
||||
}
|
||||
|
||||
+72
-12
@@ -1036,6 +1036,78 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_error_if_exists(options, 1);
|
||||
}
|
||||
|
||||
StartPhase("checkpoint_export_column_family");
|
||||
{
|
||||
static char cf_export_path[200];
|
||||
static char db_import_path[200];
|
||||
snprintf(cf_export_path, sizeof(cf_export_path),
|
||||
"%s/rocksdb_c_test-%d-cf_export", GetTempDir(), ((int)geteuid()));
|
||||
snprintf(db_import_path, sizeof(db_import_path),
|
||||
"%s/rocksdb_c_test-%d-db_import", GetTempDir(), ((int)geteuid()));
|
||||
|
||||
rocksdb_options_t* db_options = rocksdb_options_create();
|
||||
rocksdb_column_family_handle_t* cf_export =
|
||||
rocksdb_create_column_family(db, db_options, "cf_export", &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_put_cf(db, woptions, cf_export, "k1", 2, "v1", 2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put_cf(db, woptions, cf_export, "k2", 2, "v2", 2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_checkpoint_t* checkpoint =
|
||||
rocksdb_checkpoint_object_create(db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_export_import_files_metadata_t* export_metadata =
|
||||
rocksdb_checkpoint_export_column_family(checkpoint, cf_export,
|
||||
cf_export_path, &err);
|
||||
CheckNoError(err);
|
||||
const char* comparator_name =
|
||||
rocksdb_export_import_files_metadata_get_db_comparator_name(
|
||||
export_metadata);
|
||||
CheckEqual("leveldb.BytewiseComparator", comparator_name, 26);
|
||||
rocksdb_free((void*)comparator_name);
|
||||
rocksdb_checkpoint_object_destroy(checkpoint);
|
||||
checkpoint = NULL;
|
||||
rocksdb_drop_column_family(db, cf_export, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_column_family_handle_destroy(cf_export);
|
||||
rocksdb_options_set_create_if_missing(db_options, 1);
|
||||
rocksdb_options_set_error_if_exists(db_options, 1);
|
||||
rocksdb_t* db_import = rocksdb_open(db_options, db_import_path, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_import_column_family_options_t* import_options =
|
||||
rocksdb_import_column_family_options_create();
|
||||
rocksdb_column_family_handle_t* cf_import =
|
||||
rocksdb_create_column_family_with_import(db_import, db_options,
|
||||
"cf_import", import_options,
|
||||
export_metadata, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_import_column_family_options_destroy(import_options);
|
||||
rocksdb_export_import_files_metadata_destroy(export_metadata);
|
||||
size_t val_len;
|
||||
char* val =
|
||||
rocksdb_get_cf(db_import, roptions, cf_import, "k1", 2, &val_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual("v1", val, val_len);
|
||||
free(val);
|
||||
|
||||
val =
|
||||
rocksdb_get_cf(db_import, roptions, cf_import, "k2", 2, &val_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual("v2", val, val_len);
|
||||
free(val);
|
||||
|
||||
rocksdb_column_family_handle_destroy(cf_import);
|
||||
cf_import = NULL;
|
||||
rocksdb_close(db_import);
|
||||
rocksdb_destroy_db(db_options, db_import_path, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_options_destroy(db_options);
|
||||
db_options = NULL;
|
||||
}
|
||||
|
||||
StartPhase("compactall");
|
||||
rocksdb_compact_range(db, NULL, 0, NULL, 0);
|
||||
CheckGet(db, roptions, "foo", "hello");
|
||||
@@ -2230,10 +2302,6 @@ int main(int argc, char** argv) {
|
||||
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));
|
||||
|
||||
@@ -2493,8 +2561,6 @@ 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));
|
||||
@@ -2681,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));
|
||||
|
||||
+3
-2
@@ -280,7 +280,8 @@ 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;
|
||||
}
|
||||
|
||||
@@ -1331,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,
|
||||
|
||||
@@ -600,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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -284,9 +284,9 @@ Compaction::Compaction(
|
||||
CompressionOptions _compression_opts, Temperature _output_temperature,
|
||||
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),
|
||||
@@ -304,7 +304,9 @@ Compaction::Compaction(
|
||||
output_compression_(_compression),
|
||||
output_compression_opts_(_compression_opts),
|
||||
output_temperature_(_output_temperature),
|
||||
deletion_compaction_(_deletion_compaction),
|
||||
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 +323,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 +352,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 +647,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++) {
|
||||
|
||||
@@ -94,10 +94,9 @@ class Compaction {
|
||||
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);
|
||||
|
||||
@@ -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,6 +265,10 @@ 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(); }
|
||||
Status InputStatus() const { return input_.status(); }
|
||||
|
||||
@@ -347,7 +362,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 +431,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 +449,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
|
||||
|
||||
+569
-368
File diff suppressed because it is too large.
Load diff
@@ -196,7 +196,7 @@ class CompactionJob {
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
protected:
|
||||
void UpdateCompactionJobOutputStats(
|
||||
void UpdateCompactionJobOutputStatsFromInternalStats(
|
||||
const InternalStats::CompactionStatsFull& internal_stats) const;
|
||||
|
||||
void LogCompaction();
|
||||
@@ -225,7 +225,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 +242,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
|
||||
@@ -278,9 +279,135 @@ 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 InitializeReadOptions(ColumnFamilyData* cfd, 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);
|
||||
|
||||
|
||||
@@ -651,7 +651,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);
|
||||
|
||||
@@ -56,6 +56,7 @@ Status CompactionOutputs::Finish(
|
||||
stats_.bytes_written += current_bytes;
|
||||
stats_.bytes_written_pre_comp += builder_->PreCompressionSize();
|
||||
stats_.num_output_files = static_cast<int>(outputs_.size());
|
||||
worker_cpu_micros_ += builder_->GetWorkerCPUMicros();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -168,6 +168,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;
|
||||
@@ -296,6 +300,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
|
||||
@@ -380,8 +382,8 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
mutable_cf_options.default_write_temperature,
|
||||
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 +603,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 +619,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
|
||||
@@ -680,10 +682,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
mutable_cf_options.default_write_temperature,
|
||||
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);
|
||||
|
||||
@@ -873,9 +874,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
mutable_cf_options.default_write_temperature,
|
||||
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);
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -278,18 +283,17 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -127,11 +127,9 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -200,11 +198,10 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
mutable_cf_options.default_write_temperature,
|
||||
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 +258,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];
|
||||
@@ -297,11 +297,9 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.default_write_temperature,
|
||||
/* 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 +390,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 +416,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;
|
||||
}
|
||||
|
||||
@@ -446,7 +445,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,
|
||||
|
||||
@@ -26,16 +26,14 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
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_;
|
||||
@@ -561,9 +560,8 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* 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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2689,13 +2696,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());
|
||||
@@ -3627,7 +3635,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,
|
||||
@@ -3831,9 +3839,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.
|
||||
@@ -3942,9 +3951,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();
|
||||
@@ -3988,9 +3998,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();
|
||||
@@ -4030,9 +4041,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();
|
||||
@@ -4077,9 +4089,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();
|
||||
@@ -4125,9 +4138,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();
|
||||
@@ -4176,9 +4190,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.
|
||||
@@ -4234,9 +4249,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);
|
||||
@@ -4252,9 +4268,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());
|
||||
}
|
||||
@@ -4290,9 +4307,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);
|
||||
@@ -4310,9 +4328,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());
|
||||
}
|
||||
@@ -4350,9 +4369,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);
|
||||
@@ -4370,9 +4390,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());
|
||||
|
||||
@@ -48,7 +48,9 @@ class UniversalCompactionBuilder {
|
||||
vstorage_(vstorage),
|
||||
picker_(picker),
|
||||
log_buffer_(log_buffer),
|
||||
require_max_output_level_(require_max_output_level) {
|
||||
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);
|
||||
@@ -422,8 +424,7 @@ class UniversalCompactionBuilder {
|
||||
bool MeetsOutputLevelRequirements(int output_level) const {
|
||||
return !require_max_output_level_ ||
|
||||
Compaction::OutputToNonZeroMaxOutputLevel(
|
||||
output_level,
|
||||
vstorage_->MaxOutputLevel(ioptions_.allow_ingest_behind));
|
||||
output_level, vstorage_->MaxOutputLevel(allow_ingest_behind_));
|
||||
}
|
||||
|
||||
const ImmutableOptions& ioptions_;
|
||||
@@ -437,7 +438,6 @@ class UniversalCompactionBuilder {
|
||||
VersionStorageInfo* vstorage_;
|
||||
UniversalCompactionPicker* picker_;
|
||||
LogBuffer* log_buffer_;
|
||||
bool require_max_output_level_;
|
||||
// Optional earliest snapshot at time of compaction picking. This is only
|
||||
// provided if the column family doesn't enable user-defined timestamps.
|
||||
// And this information is only passed to `Compaction` picked by deletion
|
||||
@@ -448,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,
|
||||
@@ -733,8 +735,7 @@ bool UniversalCompactionBuilder::ShouldSkipMarkedFile(
|
||||
Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
const int kLevel0 = 0;
|
||||
score_ = vstorage_->CompactionScore(kLevel0);
|
||||
const 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 =
|
||||
@@ -781,8 +782,7 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
"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 ==
|
||||
@@ -1024,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) {
|
||||
@@ -1097,10 +1096,9 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* 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
|
||||
@@ -1447,10 +1445,9 @@ Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
/* 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
|
||||
@@ -1519,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++) {
|
||||
@@ -1600,11 +1596,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* 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(
|
||||
@@ -1625,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);
|
||||
@@ -1700,10 +1693,9 @@ Compaction* UniversalCompactionBuilder::PickCompactionWithSortedRunRange(
|
||||
mutable_cf_options_.default_write_temperature,
|
||||
/* 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() {
|
||||
|
||||
@@ -408,7 +408,7 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
// 2. Update job-level output stats with the aggregated internal_stats_
|
||||
// Please note that input stats will be updated by primary host when all
|
||||
// subcompactions are finished
|
||||
UpdateCompactionJobOutputStats(internal_stats_);
|
||||
UpdateCompactionJobOutputStatsFromInternalStats(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();
|
||||
|
||||
@@ -461,6 +461,97 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
ASSERT_EQ(handles_[1]->GetName(), info.cf_name);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, StandaloneDeleteRangeTombstoneOptimization) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = CompactionStyle::kCompactionStyleUniversal;
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
|
||||
ASSERT_EQ(num_files_after_filtered, 1);
|
||||
|
||||
Close();
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, CompactionOutputFileIOError) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -793,6 +884,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;
|
||||
|
||||
@@ -191,6 +191,14 @@ class SubcompactionState {
|
||||
return &compaction_outputs_.stats_;
|
||||
}
|
||||
|
||||
uint64_t GetWorkerCPUMicros() const {
|
||||
uint64_t rv = compaction_outputs_.GetWorkerCPUMicros();
|
||||
if (compaction->SupportsPerKeyPlacement()) {
|
||||
rv += proximal_level_outputs_.GetWorkerCPUMicros();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
CompactionRangeDelAggregator* RangeDelAgg() const {
|
||||
return range_del_agg_.get();
|
||||
}
|
||||
|
||||
@@ -1764,7 +1764,10 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
options.env = mock_env_.get();
|
||||
options.level0_file_num_compaction_trigger = kNumTrigger;
|
||||
options.num_levels = kNumLevels;
|
||||
options.last_level_temperature = Temperature::kCold;
|
||||
// This existing test selected to also check the case of various temperatures
|
||||
// for last_level_temperature, which should not be interesting enough to
|
||||
// exercise across many/all test cases
|
||||
options.last_level_temperature = RandomKnownTemperature();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -1791,6 +1794,10 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
ASSERT_FALSE(tp_mapping.Empty());
|
||||
auto seqs = tp_mapping.TEST_GetInternalMapping();
|
||||
ASSERT_FALSE(seqs.empty());
|
||||
ASSERT_GE(GetSstSizeHelper(Temperature::kUnknown), 1);
|
||||
for (auto t : kKnownTemperatures) {
|
||||
ASSERT_EQ(GetSstSizeHelper(t), 0);
|
||||
}
|
||||
|
||||
// Wait more than preclude_last_level time, then make sure all the data is
|
||||
// compacted to the last level even there's no write (no seqno -> time
|
||||
@@ -1799,8 +1806,14 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
|
||||
ASSERT_GT(GetSstSizeHelper(Temperature::kCold), 0);
|
||||
|
||||
for (auto t : kKnownTemperatures) {
|
||||
if (t == options.last_level_temperature) {
|
||||
ASSERT_GT(GetSstSizeHelper(t), 0);
|
||||
} else {
|
||||
ASSERT_EQ(GetSstSizeHelper(t), 0);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
+74
-3
@@ -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);
|
||||
|
||||
|
||||
@@ -5087,6 +5087,7 @@ TEST_F(DBBasicTest, DisallowMemtableWrite) {
|
||||
Options options_disallow = options_allow;
|
||||
options_disallow.disallow_memtable_writes = true;
|
||||
options_disallow.paranoid_memory_checks = true;
|
||||
options_disallow.memtable_veirfy_per_key_checksum_on_seek = true;
|
||||
|
||||
DestroyAndReopen(options_allow);
|
||||
// CFs allowing and disallowing memtable write
|
||||
|
||||
+448
-1
@@ -19,6 +19,7 @@
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/concurrent_task_limiter.h"
|
||||
#include "rocksdb/experimental.h"
|
||||
#include "rocksdb/iostats_context.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "test_util/mock_time_env.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -74,6 +75,43 @@ class CompactionStatsCollector : public EventListener {
|
||||
std::vector<std::atomic<int>> compaction_completed_;
|
||||
};
|
||||
|
||||
class DeletionTriggeredCompactionWithMinFileSizeTestListener
|
||||
: public EventListener {
|
||||
public:
|
||||
explicit DeletionTriggeredCompactionWithMinFileSizeTestListener(
|
||||
uint64_t min_file_size)
|
||||
: min_file_size_(min_file_size) {}
|
||||
|
||||
void OnCompactionBegin(DB* db, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.compaction_reason,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
|
||||
auto env = db->GetEnv();
|
||||
const std::vector<DbPath>& db_paths = db->GetOptions().db_paths;
|
||||
for (const auto& file : ci.input_file_infos) {
|
||||
uint64_t file_size = GetSstFileSize(env, db_paths, file.file_number);
|
||||
|
||||
// Assert that the file size respects the minimum threshold
|
||||
ASSERT_GE(file_size, min_file_size_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static uint64_t GetSstFileSize(Env* env, const std::vector<DbPath>& db_paths,
|
||||
uint64_t file_number) {
|
||||
uint32_t path_id = 0; // since only one path
|
||||
std::string sst_file_name = TableFileName(db_paths, file_number, path_id);
|
||||
uint64_t file_size = 0;
|
||||
Status s = env->GetFileSize(sst_file_name, &file_size);
|
||||
if (!s.ok()) {
|
||||
return 0;
|
||||
}
|
||||
return file_size;
|
||||
}
|
||||
|
||||
uint64_t min_file_size_;
|
||||
};
|
||||
|
||||
class DBCompactionTest : public DBTestBase {
|
||||
public:
|
||||
DBCompactionTest()
|
||||
@@ -1371,6 +1409,89 @@ TEST_F(DBCompactionTest, RecoverDuringMemtableCompaction) {
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactionWithDeletionsAndMinFileSize) {
|
||||
const uint64_t kMinFileSize = 32 * 1024; // 32KB
|
||||
const int kDeletionTriggerCount = 50;
|
||||
const int kInitialKeyCount = 100;
|
||||
const int kAdditionalKeyCount = 50;
|
||||
const int kValueSize = 1024;
|
||||
const int kSmallValueSize = 512;
|
||||
const int kSeed = 301;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.write_buffer_size = 1024 * 1024; // 1MB
|
||||
options.level0_file_num_compaction_trigger = 100;
|
||||
|
||||
options.table_properties_collector_factories = {
|
||||
NewCompactOnDeletionCollectorFactory(
|
||||
kInitialKeyCount /* sliding window size */, kDeletionTriggerCount,
|
||||
0.5 /* deletion ratio */, kMinFileSize)};
|
||||
auto listener =
|
||||
new DeletionTriggeredCompactionWithMinFileSizeTestListener(kMinFileSize);
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(kSeed);
|
||||
|
||||
// Create a large file that will be subject to DTC later
|
||||
for (int i = 0; i < kInitialKeyCount; i++) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<LiveFileMetaData> initial_metadata;
|
||||
db_->GetLiveFilesMetaData(&initial_metadata);
|
||||
ASSERT_EQ(initial_metadata.size(), 1);
|
||||
|
||||
// Create small files that should not trigger compaction
|
||||
ASSERT_OK(Put("small_file_key1", rnd.RandomString(kSmallValueSize)));
|
||||
ASSERT_OK(Put("small_file_key2", rnd.RandomString(kSmallValueSize)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Delete("small_file_key1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Create a file with enough deletions and size to trigger DTC
|
||||
// Delete keys from the large file to reach deletion threshold
|
||||
for (int i = 0; i < kDeletionTriggerCount; i++) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
|
||||
// Add new keys to ensure the deletion file meets the min_file_size threshold
|
||||
for (int i = kInitialKeyCount; i < kInitialKeyCount + kAdditionalKeyCount;
|
||||
i++) {
|
||||
ASSERT_OK(Put(Key(i), rnd.RandomString(kValueSize)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Verify file count after compaction
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 2); // Small file and deletion file
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 1); // Compacted large file
|
||||
|
||||
// Verify deleted keys are gone
|
||||
for (int i = 0; i < kDeletionTriggerCount; i++) {
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &value).IsNotFound());
|
||||
}
|
||||
|
||||
// Verify non-deleted keys from large file are still accessible
|
||||
for (int i = kDeletionTriggerCount; i < kInitialKeyCount; i++) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), Key(i), &value));
|
||||
ASSERT_EQ(value.size(), kValueSize);
|
||||
}
|
||||
|
||||
// Verify new keys are accessible
|
||||
for (int i = kInitialKeyCount; i < kInitialKeyCount + kAdditionalKeyCount;
|
||||
i++) {
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), Key(i), &value));
|
||||
ASSERT_EQ(value.size(), kValueSize);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) {
|
||||
int32_t trivial_move = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -6748,7 +6869,11 @@ INSTANTIATE_TEST_CASE_P(RoundRobinSubcompactionsAgainstPressureToken,
|
||||
RoundRobinSubcompactionsAgainstPressureToken,
|
||||
testing::Bool());
|
||||
|
||||
TEST_P(RoundRobinSubcompactionsAgainstResources, SubcompactionsUsingResources) {
|
||||
// FIXME: the test is flaky and failing the assertion
|
||||
// ASSERT_EQ(actual_reserved_threads, expected_reserved_threads);
|
||||
// It's likely a test set up issue, fix if we are to use RoubdRobin compaction.
|
||||
TEST_P(RoundRobinSubcompactionsAgainstResources,
|
||||
DISABLED_SubcompactionsUsingResources) {
|
||||
const int kKeysPerBuffer = 200;
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 4;
|
||||
@@ -7004,6 +7129,70 @@ TEST_F(DBCompactionTest, PartialManualCompaction) {
|
||||
ASSERT_OK(dbfull()->CompactRange(cro, nullptr, nullptr));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ConcurrentFIFOPickingSameFileBug) {
|
||||
Options opts = CurrentOptions();
|
||||
opts.compaction_style = CompactionStyle::kCompactionStyleLevel;
|
||||
opts.num_levels = 3;
|
||||
opts.disable_auto_compactions = true;
|
||||
opts.max_background_jobs = 3;
|
||||
|
||||
DestroyAndReopen(opts);
|
||||
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Create a non-L0 SST file for multi-level FIFO size-based compaction later
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
Options opts_new(opts);
|
||||
opts_new.compaction_style = CompactionStyle::kCompactionStyleFIFO;
|
||||
opts_new.max_open_files = -1;
|
||||
// Set a low threshold to trigger multi-level size-based compaction
|
||||
opts_new.compaction_options_fifo.max_table_files_size = 1;
|
||||
|
||||
Reopen(opts_new);
|
||||
|
||||
const CompactRangeOptions cro;
|
||||
const Slice begin_key("k1");
|
||||
const Slice end_key("k2");
|
||||
|
||||
std::unique_ptr<port::Thread> concurrent_compaction;
|
||||
|
||||
bool within_first_compaction = true;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"VersionSet::LogAndApply:WriteManifestStart", [&](void* /*arg*/) {
|
||||
if (!within_first_compaction) {
|
||||
return;
|
||||
}
|
||||
within_first_compaction = false;
|
||||
|
||||
// To allow the second/concurrent compaction to still see the non-L0
|
||||
// SST file and coerce the bug of picking that file
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::BackgroundCompaction:BeforeCompaction",
|
||||
"VersionSet::LogAndApply:WriteManifest"},
|
||||
});
|
||||
|
||||
concurrent_compaction.reset(new port::Thread([&]() {
|
||||
// Before the fix, the second CompactRange() will either fail the
|
||||
// assertion of double file picking `being_compacted !=
|
||||
// inputs_[i][j]->being_compacted` in debug mode or cause LSM shape
|
||||
// corruption "Cannot delete table file XXX from level 2 since it is
|
||||
// not in the LSM tree" in release mode
|
||||
Status s = db_->CompactRange(cro, &begin_key, &end_key);
|
||||
ASSERT_OK(s);
|
||||
}));
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Status s = db_->CompactRange(cro, &begin_key, &end_key);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_OK(s);
|
||||
|
||||
concurrent_compaction->join();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ManualCompactionFailsInReadOnlyMode) {
|
||||
// Regression test for bug where manual compaction hangs forever when the DB
|
||||
// is in read-only mode. Verify it now at least returns, despite failing.
|
||||
@@ -9603,6 +9792,7 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
|
||||
int total_cold = 0;
|
||||
int total_warm = 0;
|
||||
int total_hot = 0;
|
||||
int total_ice = 0;
|
||||
int total_unknown = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
|
||||
@@ -9613,6 +9803,8 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
|
||||
total_warm++;
|
||||
} else if (temperature == Temperature::kHot) {
|
||||
total_hot++;
|
||||
} else if (temperature == Temperature::kIce) {
|
||||
total_ice++;
|
||||
} else {
|
||||
assert(temperature == Temperature::kUnknown);
|
||||
total_unknown++;
|
||||
@@ -9686,6 +9878,261 @@ TEST_F(DBCompactionTest, FIFOChangeTemperature) {
|
||||
}
|
||||
}
|
||||
|
||||
using TemperatureSet = SmallEnumSet<Temperature, Temperature::kLastTemperature>;
|
||||
static void VerifyTemperatureFileReadStats(const Statistics& st,
|
||||
TemperatureSet temps) {
|
||||
SCOPED_TRACE("Temp set size = " + std::to_string(temps.count()));
|
||||
constexpr uint64_t min_bytes = 100;
|
||||
constexpr uint64_t min_count = 1;
|
||||
|
||||
IOStatsContext* iostats = get_iostats_context();
|
||||
if (temps.Contains(Temperature::kHot)) {
|
||||
EXPECT_GE(st.getTickerCount(HOT_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(HOT_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.hot_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.hot_file_read_count,
|
||||
min_count);
|
||||
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(HOT_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(HOT_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
|
||||
}
|
||||
|
||||
if (temps.Contains(Temperature::kWarm)) {
|
||||
EXPECT_GE(st.getTickerCount(WARM_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(WARM_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.warm_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.warm_file_read_count,
|
||||
min_count);
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(WARM_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(WARM_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
|
||||
}
|
||||
|
||||
if (temps.Contains(Temperature::kCool)) {
|
||||
EXPECT_GE(st.getTickerCount(COOL_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(COOL_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.cool_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.cool_file_read_count,
|
||||
min_count);
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(COOL_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(COOL_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.cool_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.cool_file_read_count, 0);
|
||||
}
|
||||
|
||||
if (temps.Contains(Temperature::kCold)) {
|
||||
EXPECT_GE(st.getTickerCount(COLD_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(COLD_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.cold_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.cold_file_read_count,
|
||||
min_count);
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(COLD_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(COLD_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.cold_file_read_count, 0);
|
||||
}
|
||||
|
||||
if (temps.Contains(Temperature::kIce)) {
|
||||
EXPECT_GE(st.getTickerCount(ICE_FILE_READ_BYTES), min_bytes);
|
||||
EXPECT_GE(st.getTickerCount(ICE_FILE_READ_COUNT), min_count);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.ice_file_bytes_read,
|
||||
min_bytes);
|
||||
EXPECT_GE(iostats->file_io_stats_by_temperature.ice_file_read_count,
|
||||
min_count);
|
||||
} else {
|
||||
EXPECT_EQ(st.getTickerCount(ICE_FILE_READ_BYTES), 0);
|
||||
EXPECT_EQ(st.getTickerCount(ICE_FILE_READ_COUNT), 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.ice_file_bytes_read, 0);
|
||||
EXPECT_EQ(iostats->file_io_stats_by_temperature.ice_file_read_count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
|
||||
// Test multi-tier aging: Hot -> Warm -> Cool -> Cold -> Ice
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.num_levels = 1;
|
||||
options.max_open_files = -1;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.create_if_missing = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.no_block_cache = true; // Simplify statistics
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
|
||||
CompactionOptionsFIFO fifo_options;
|
||||
// Multi-tier aging: files age through multiple temperatures
|
||||
fifo_options.file_temperature_age_thresholds = {
|
||||
{Temperature::kWarm, 500}, // Hot -> Warm after 500s
|
||||
{Temperature::kCool, 1000}, // Warm -> Cool
|
||||
{Temperature::kCold, 1500}, // Cool -> Cold
|
||||
{Temperature::kIce, 2000} // Cold -> Ice
|
||||
};
|
||||
fifo_options.max_table_files_size = 100000000;
|
||||
fifo_options.allow_trivial_copy_when_change_temperature = true;
|
||||
options.compaction_options_fifo = fifo_options;
|
||||
options.default_write_temperature = Temperature::kHot;
|
||||
|
||||
Reopen(options);
|
||||
env_->SetMockSleep();
|
||||
|
||||
// Track all temperature file creations
|
||||
int total_hot = 0, total_warm = 0, total_cool = 0, total_cold = 0,
|
||||
total_ice = 0, total_unknown = 0;
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
|
||||
Temperature temperature = *(static_cast<Temperature*>(arg));
|
||||
switch (temperature) {
|
||||
case Temperature::kHot:
|
||||
total_hot++;
|
||||
break;
|
||||
case Temperature::kWarm:
|
||||
total_warm++;
|
||||
break;
|
||||
case Temperature::kCool:
|
||||
total_cool++;
|
||||
break;
|
||||
case Temperature::kCold:
|
||||
total_cold++;
|
||||
break;
|
||||
case Temperature::kIce:
|
||||
total_ice++;
|
||||
break;
|
||||
case Temperature::kUnknown:
|
||||
total_unknown++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Create initial three files (will start as Hot), enough to ensure key
|
||||
// range filtering will be applied in FilePicker::GetNextFile() with one
|
||||
// more file
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
ASSERT_OK(Put(Key(0), Random::GetTLSInstance()->RandomBinaryString(100)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Test reading from Hot temperature file
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kHot);
|
||||
|
||||
// Land well into each time interval
|
||||
env_->MockSleepForSeconds(100);
|
||||
|
||||
// Age initial files to warm
|
||||
env_->MockSleepForSeconds(500);
|
||||
ASSERT_OK(Put(Key(1), Random::GetTLSInstance()->RandomBinaryString(101)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Test reading from Warm temperature file (the aged file)
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
// Verify Warm file statistics
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kWarm);
|
||||
|
||||
// Age initial files to cool
|
||||
env_->MockSleepForSeconds(500);
|
||||
ASSERT_OK(Put(Key(2), Random::GetTLSInstance()->RandomBinaryString(102)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Test reading from Cool temperature file (the aged file)
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kCool);
|
||||
|
||||
// Age initial files to cold
|
||||
env_->MockSleepForSeconds(500);
|
||||
ASSERT_OK(Put(Key(3), Random::GetTLSInstance()->RandomBinaryString(103)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Test reading from Cold temperature file (the aged file)
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kCold);
|
||||
|
||||
// Age initial files to ice
|
||||
env_->MockSleepForSeconds(500);
|
||||
ASSERT_OK(Put(Key(4), Random::GetTLSInstance()->RandomBinaryString(104)));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Test reading from Ice temperature file (the aged file)
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
ASSERT_EQ(100U, Get(Key(0)).size());
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kIce);
|
||||
|
||||
// Verify temperature progression in metadata
|
||||
ColumnFamilyMetaData metadata;
|
||||
db_->GetColumnFamilyMetaData(&metadata);
|
||||
|
||||
// Should have files at different temperatures
|
||||
std::map<Temperature, int> temp_counts;
|
||||
for (const auto& file : metadata.levels[0].files) {
|
||||
temp_counts[file.temperature]++;
|
||||
}
|
||||
|
||||
// Verify current files temperatures
|
||||
EXPECT_EQ(temp_counts[Temperature::kHot], 1);
|
||||
EXPECT_EQ(temp_counts[Temperature::kWarm], 1);
|
||||
EXPECT_EQ(temp_counts[Temperature::kCool], 1);
|
||||
EXPECT_EQ(temp_counts[Temperature::kCold], 1);
|
||||
EXPECT_EQ(temp_counts[Temperature::kIce], 3);
|
||||
|
||||
// Verify historical (and current) file temperatures
|
||||
EXPECT_EQ(total_hot, 7);
|
||||
EXPECT_EQ(total_warm, 6);
|
||||
EXPECT_EQ(total_cool, 5);
|
||||
EXPECT_EQ(total_cold, 4);
|
||||
EXPECT_EQ(total_ice, 3);
|
||||
|
||||
// Final comprehensive test: read from all temperature files
|
||||
Reopen(options);
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
get_iostats_context()->Reset();
|
||||
|
||||
// Read from all files to verify cumulative statistics
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_EQ(static_cast<unsigned>(100 + i), Get(Key(i)).size());
|
||||
}
|
||||
|
||||
VerifyTemperatureFileReadStats(*options.statistics, TemperatureSet::All());
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DisableMultiManualCompaction) {
|
||||
const int kNumL0Files = 10;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+24
-103
@@ -1069,7 +1069,7 @@ void DBImpl::DumpStats() {
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (auto cfd : versions_->GetRefedColumnFamilySet()) {
|
||||
if (!cfd->initialized()) {
|
||||
if (!cfd->initialized() || cfd->IsDropped()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1245,13 +1245,11 @@ Status DBImpl::SetOptions(
|
||||
WriteOptionsFile(write_options, true /*db_mutex_already_held*/);
|
||||
bg_cv_.SignalAll();
|
||||
|
||||
#if __cplusplus >= 202002L
|
||||
assert(new_options_copy == cfd->GetLatestMutableCFOptions());
|
||||
assert(cfd->GetLatestMutableCFOptions() ==
|
||||
cfd->GetCurrentMutableCFOptions());
|
||||
assert(cfd->GetCurrentMutableCFOptions() ==
|
||||
cfd->current()->GetMutableCFOptions());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
sv_context.Clean();
|
||||
@@ -3835,11 +3833,9 @@ bool DBImpl::KeyMayExist(const ReadOptions& read_options,
|
||||
|
||||
std::unique_ptr<MultiScan> DBImpl::NewMultiScan(
|
||||
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
|
||||
const std::vector<ScanOptions>& scan_opts) {
|
||||
std::unique_ptr<Iterator> iter(NewIterator(_read_options, column_family));
|
||||
iter->Prepare(scan_opts);
|
||||
std::unique_ptr<MultiScan> ms_iter =
|
||||
std::make_unique<MultiScan>(scan_opts, std::move(iter));
|
||||
const MultiScanArgs& scan_opts) {
|
||||
std::unique_ptr<MultiScan> ms_iter = std::make_unique<MultiScan>(
|
||||
_read_options, scan_opts, this, column_family);
|
||||
return ms_iter;
|
||||
}
|
||||
|
||||
@@ -4347,7 +4343,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()
|
||||
@@ -4367,8 +4363,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(
|
||||
@@ -5057,85 +5052,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();
|
||||
@@ -5842,10 +5758,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) {
|
||||
@@ -5853,6 +5765,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) {
|
||||
@@ -6087,18 +6007,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -386,7 +386,7 @@ class DBImpl : public DB {
|
||||
using DB::NewMultiScan;
|
||||
std::unique_ptr<MultiScan> NewMultiScan(
|
||||
const ReadOptions& _read_options, ColumnFamilyHandle* column_family,
|
||||
const std::vector<ScanOptions>& scan_opts) override;
|
||||
const MultiScanArgs& scan_opts) override;
|
||||
|
||||
const Snapshot* GetSnapshot() override;
|
||||
void ReleaseSnapshot(const Snapshot* snapshot) override;
|
||||
@@ -804,10 +804,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
|
||||
@@ -1392,6 +1388,9 @@ class DBImpl : public DB {
|
||||
// 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_;
|
||||
|
||||
@@ -1111,8 +1111,7 @@ Status DBImpl::CompactRangeInternal(const CompactRangeOptions& options,
|
||||
cfd->NumberLevels() > 1) {
|
||||
// Always compact all files together.
|
||||
final_output_level = cfd->NumberLevels() - 1;
|
||||
// if bottom most level is reserved
|
||||
if (immutable_db_options_.allow_ingest_behind) {
|
||||
if (cfd->AllowIngestBehind()) {
|
||||
final_output_level--;
|
||||
}
|
||||
s = RunManualCompaction(cfd, ColumnFamilyData::kCompactAllLevels,
|
||||
@@ -1460,7 +1459,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
}
|
||||
}
|
||||
|
||||
if (cfd->ioptions().allow_ingest_behind &&
|
||||
if (cfd->AllowIngestBehind() &&
|
||||
output_level >= cfd->ioptions().num_levels - 1) {
|
||||
return Status::InvalidArgument(
|
||||
"Exceed the maximum output level defined by "
|
||||
@@ -1500,7 +1499,7 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
std::unique_ptr<Compaction> c;
|
||||
assert(cfd->compaction_picker());
|
||||
c.reset(cfd->compaction_picker()->CompactFiles(
|
||||
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
|
||||
compact_options, input_files, output_level, version->storage_info(),
|
||||
cfd->GetLatestMutableCFOptions(), mutable_db_options_, output_path_id));
|
||||
// we already sanitized the set of input files and checked for conflicts
|
||||
@@ -1849,10 +1848,9 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
0 /* max_subcompactions, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
false /* is manual */, "" /* trim_ts */, -1 /* score, not applicable */,
|
||||
false /* is deletion compaction, not applicable */,
|
||||
false /* l0_files_might_overlap, not applicable */,
|
||||
CompactionReason::kRefitLevel));
|
||||
CompactionReason::kRefitLevel, "" /* trim_ts */,
|
||||
-1 /* score, not applicable */,
|
||||
false /* l0_files_might_overlap, not applicable */));
|
||||
cfd->compaction_picker()->RegisterCompaction(c.get());
|
||||
TEST_SYNC_POINT("DBImpl::ReFitLevel:PostRegisterCompaction");
|
||||
VersionEdit edit;
|
||||
@@ -4156,6 +4154,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
->current()
|
||||
->storage_info()
|
||||
->MaxOutputLevel(
|
||||
c->immutable_options().cf_allow_ingest_behind ||
|
||||
immutable_db_options_.allow_ingest_behind)) &&
|
||||
env_->GetBackgroundThreads(Env::Priority::BOTTOM) > 0) {
|
||||
assert(thread_pri == Env::Priority::LOW);
|
||||
@@ -4424,13 +4423,14 @@ Compaction* DBImpl::CreateIntendedCompactionForwardedToBottomPriorityPool(
|
||||
|
||||
c->ReleaseCompactionFiles(Status::OK());
|
||||
|
||||
Compaction* intended_compaction = new Compaction(
|
||||
vstorage, io, mo, mutable_db_options_, std::move(inputs),
|
||||
c->output_level(), c->target_output_file_size(),
|
||||
c->max_compaction_bytes(), c->output_path_id(), c->output_compression(),
|
||||
c->output_compression_opts(), c->output_temperature(),
|
||||
c->max_subcompactions(), c->grandparents(),
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */);
|
||||
Compaction* intended_compaction =
|
||||
new Compaction(vstorage, io, mo, mutable_db_options_, std::move(inputs),
|
||||
c->output_level(), c->target_output_file_size(),
|
||||
c->max_compaction_bytes(), c->output_path_id(),
|
||||
c->output_compression(), c->output_compression_opts(),
|
||||
c->output_temperature(), c->max_subcompactions(),
|
||||
c->grandparents(), std::nullopt /* earliest_snapshot */,
|
||||
nullptr /* snapshot_checker */, c->compaction_reason());
|
||||
|
||||
cfd->compaction_picker()->RegisterCompaction(intended_compaction);
|
||||
vstorage->ComputeCompactionScore(io, mo);
|
||||
@@ -4660,7 +4660,7 @@ void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
bottommost_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
standalone_range_deletion_files_mark_threshold_ = kMaxSequenceNumber;
|
||||
for (auto* my_cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (!my_cfd->ioptions().allow_ingest_behind) {
|
||||
if (!my_cfd->AllowIngestBehind()) {
|
||||
bottommost_files_mark_threshold_ = std::min(
|
||||
bottommost_files_mark_threshold_,
|
||||
my_cfd->current()->storage_info()->bottommost_files_mark_threshold());
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,9 +49,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()) {
|
||||
@@ -653,49 +650,6 @@ 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);
|
||||
Status s;
|
||||
@@ -894,7 +848,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(
|
||||
@@ -913,13 +866,27 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
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(
|
||||
c.reset(cfd->compaction_picker()->PickCompactionForCompactFiles(
|
||||
comp_options, input_files, input.output_level, vstorage,
|
||||
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0));
|
||||
cfd->GetLatestMutableCFOptions(), mutable_db_options_, 0,
|
||||
/*earliest_snapshot=*/job_context.snapshot_seqs.empty()
|
||||
? kMaxSequenceNumber
|
||||
: job_context.snapshot_seqs.front(),
|
||||
job_context.snapshot_checker));
|
||||
assert(c != nullptr);
|
||||
|
||||
c->FinalizeInputInfo(version);
|
||||
|
||||
// Create output directory if it's not existed yet
|
||||
@@ -932,11 +899,6 @@ Status DBImplSecondary::CompactWithoutInstallation(
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL,
|
||||
immutable_db_options_.info_log.get());
|
||||
|
||||
const int job_id = next_job_id_.fetch_add(1);
|
||||
JobContext job_context(0, true /*create_superversion*/);
|
||||
std::vector<SequenceNumber> snapshots = input.snapshots;
|
||||
job_context.InitSnapshotContext(nullptr, nullptr, kMaxSequenceNumber,
|
||||
std::move(snapshots));
|
||||
// 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
+104
@@ -1565,11 +1565,115 @@ void DBIter::SetSavedKeyToSeekForPrevTarget(const Slice& target) {
|
||||
}
|
||||
}
|
||||
|
||||
Status DBIter::ValidateScanOptions(const MultiScanArgs& multiscan_opts) const {
|
||||
if (multiscan_opts.empty()) {
|
||||
return Status::InvalidArgument("Empty MultiScanArgs");
|
||||
}
|
||||
|
||||
const std::vector<ScanOptions>& scan_opts = multiscan_opts.GetScanRanges();
|
||||
const bool has_limit = scan_opts.front().range.limit.has_value();
|
||||
if (!has_limit && scan_opts.size() > 1) {
|
||||
return Status::InvalidArgument("Scan has no upper bound");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < scan_opts.size(); ++i) {
|
||||
const auto& scan_range = scan_opts[i].range;
|
||||
if (!scan_range.start.has_value()) {
|
||||
return Status::InvalidArgument("Scan has no start key at index " +
|
||||
std::to_string(i));
|
||||
}
|
||||
|
||||
if (scan_range.limit.has_value()) {
|
||||
if (user_comparator_.CompareWithoutTimestamp(
|
||||
scan_range.start.value(), /*a_has_ts=*/false,
|
||||
scan_range.limit.value(), /*b_has_ts=*/false) >= 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Scan start key is large or equal than limit at index " +
|
||||
std::to_string(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (i > 0) {
|
||||
if (!scan_range.limit.has_value()) {
|
||||
// multiple scan without limit scan ranges
|
||||
return Status::InvalidArgument("Scan has no upper bound at index " +
|
||||
std::to_string(i));
|
||||
}
|
||||
|
||||
const auto& last_end_key = scan_opts[i - 1].range.limit.value();
|
||||
if (user_comparator_.CompareWithoutTimestamp(
|
||||
scan_range.start.value(), /*a_has_ts=*/false, last_end_key,
|
||||
/*b_has_ts=*/false) < 0) {
|
||||
return Status::InvalidArgument("Overlapping ranges at index " +
|
||||
std::to_string(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void DBIter::Prepare(const MultiScanArgs& scan_opts) {
|
||||
status_ = ValidateScanOptions(scan_opts);
|
||||
if (!status_.ok()) {
|
||||
return;
|
||||
}
|
||||
std::optional<MultiScanArgs> new_scan_opts;
|
||||
new_scan_opts.emplace(scan_opts);
|
||||
scan_opts_.swap(new_scan_opts);
|
||||
scan_index_ = 0;
|
||||
if (!scan_opts.empty()) {
|
||||
iter_.Prepare(&scan_opts_.value());
|
||||
} else {
|
||||
iter_.Prepare(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
PERF_COUNTER_ADD(iter_seek_count, 1);
|
||||
PERF_CPU_TIMER_GUARD(iter_seek_cpu_nanos, clock_);
|
||||
StopWatch sw(clock_, statistics_, DB_SEEK);
|
||||
|
||||
if (scan_opts_.has_value()) {
|
||||
// Validate the seek target is as expected in the previously prepared range
|
||||
auto const& scan_ranges = scan_opts_.value().GetScanRanges();
|
||||
if (scan_index_ >= scan_ranges.size()) {
|
||||
status_ = Status::InvalidArgument(
|
||||
"Seek called after exhausting all of the scan ranges");
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate start key of next prepare range matches the seek target
|
||||
auto const& range = scan_ranges[scan_index_];
|
||||
auto const& start = range.range.start;
|
||||
assert(start.has_value());
|
||||
if (user_comparator_.CompareWithoutTimestamp(target, *start) != 0) {
|
||||
status_ = Status::InvalidArgument(
|
||||
"Seek target does not match the start of the next prepared range at "
|
||||
"index " +
|
||||
std::to_string(scan_index_));
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// validate the upper bound is set to the same value of limit, if limit
|
||||
// exists
|
||||
auto const& limit = range.range.limit;
|
||||
if (limit.has_value()) {
|
||||
if (iterate_upper_bound_ == nullptr ||
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
limit.value(), *iterate_upper_bound_) != 0) {
|
||||
status_ = Status::InvalidArgument(
|
||||
"Upper bound is not set to the same limit value of the next "
|
||||
"prepared range at index " +
|
||||
std::to_string(scan_index_));
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
scan_index_++;
|
||||
}
|
||||
|
||||
if (cfh_ != nullptr) {
|
||||
// TODO: What do we do if this returns an error?
|
||||
Slice lower_bound, upper_bound;
|
||||
|
||||
+4
-11
@@ -240,16 +240,8 @@ class DBIter final : public Iterator {
|
||||
|
||||
bool PrepareValue() override;
|
||||
|
||||
void Prepare(const std::vector<ScanOptions>& scan_opts) override {
|
||||
std::optional<std::vector<ScanOptions>> new_scan_opts;
|
||||
new_scan_opts.emplace(scan_opts);
|
||||
scan_opts_.swap(new_scan_opts);
|
||||
if (!scan_opts.empty()) {
|
||||
iter_.Prepare(&scan_opts_.value());
|
||||
} else {
|
||||
iter_.Prepare(nullptr);
|
||||
}
|
||||
}
|
||||
void Prepare(const MultiScanArgs& scan_opts) override;
|
||||
Status ValidateScanOptions(const MultiScanArgs& multiscan_opts) const;
|
||||
|
||||
private:
|
||||
DBIter(Env* _env, const ReadOptions& read_options,
|
||||
@@ -505,7 +497,8 @@ class DBIter final : public Iterator {
|
||||
const Slice* const timestamp_lb_;
|
||||
const size_t timestamp_size_;
|
||||
std::string saved_timestamp_;
|
||||
std::optional<std::vector<ScanOptions>> scan_opts_;
|
||||
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_;
|
||||
|
||||
@@ -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"
|
||||
@@ -4139,6 +4141,463 @@ TEST_P(DBIteratorTest, AverageMemtableOpsScanFlushTriggerByOverwrites) {
|
||||
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, RangeBetweenFiles) {
|
||||
auto options = CurrentOptions();
|
||||
options.target_file_size_base = 100 << 10; // 20KB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
auto rnd = Random::GetTLSInstance();
|
||||
// Write ~200KB data
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
ASSERT_OK(Put(Key(i), rnd->RandomString(2 << 10)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange({}, nullptr, nullptr));
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(49));
|
||||
|
||||
// Test with a scan range that overlaps an entire file, with upper bound
|
||||
// between 2 files
|
||||
std::vector<LiveFileMetaData> file_meta;
|
||||
dbfull()->GetLiveFilesMetaData(&file_meta);
|
||||
ASSERT_EQ(file_meta.size(), 2);
|
||||
std::vector<std::string> key_ranges(4);
|
||||
key_ranges[0] = file_meta[0].smallestkey;
|
||||
key_ranges[1] = file_meta[0].largestkey + "0";
|
||||
key_ranges[2] = file_meta[1].smallestkey + "0";
|
||||
key_ranges[3] = file_meta[1].largestkey;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
ColumnFamilyHandle* cfh = dbfull()->DefaultColumnFamily();
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[0]);
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
// Make sure exception contains the status
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
iter.reset();
|
||||
|
||||
// Test multiscan with a range entirely between adjacent files
|
||||
key_ranges[0] = file_meta[0].largestkey + "0";
|
||||
key_ranges[1] = file_meta[0].largestkey + "1";
|
||||
key_ranges[2] = file_meta[1].smallestkey + "0";
|
||||
key_ranges[3] = file_meta[1].largestkey;
|
||||
(*scan_options).clear();
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[0]);
|
||||
}
|
||||
}
|
||||
} catch (MultiScanException& ex) {
|
||||
// Make sure exception contains the status
|
||||
ASSERT_NOK(ex.status());
|
||||
std::cerr << "Iterator returned status " << ex.what();
|
||||
abort();
|
||||
} catch (std::logic_error& ex) {
|
||||
std::cerr << "Iterator returned logic error " << ex.what();
|
||||
abort();
|
||||
}
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
// This test case tests multiscan in the presence of fragmented range
|
||||
// tombstones in the LSM.
|
||||
TEST_P(DBMultiScanIteratorTest, FragmentedRangeTombstones) {
|
||||
auto options = CurrentOptions();
|
||||
// Compaction may create files 2x the target_file_size_base,
|
||||
// so set this to 50KB so we atleast end up with 2 files of
|
||||
// 100KB
|
||||
options.target_file_size_base = 50 << 10; // 50KB
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 50;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Setup the LSM as follows -
|
||||
// 1. Ingest a file with 100 keys
|
||||
// 2. Ingest a file with one overlapping key
|
||||
// 3. Do a Put and flush a file to L0 with one overlapping key
|
||||
// 4. Ingest a standalone delete range file that covers the full key space
|
||||
// and a file with the same 100 keys with new values. This will ingest
|
||||
// into L0 due to the presence of an existing file in L0
|
||||
// The final LSM will have an SST in Lmax with 100 keys, and 2 SST files
|
||||
// in Lmax-1 with half the keys each and completely overlapping delete ranges
|
||||
std::unordered_map<std::string, std::string> kvs;
|
||||
auto rnd = Random::GetTLSInstance();
|
||||
auto create_ingestion_data_file_and_update_key_value =
|
||||
[&](const std::string& filename, int start_key, int end_key) {
|
||||
std::unique_ptr<SstFileWriter> writer;
|
||||
writer.reset(new SstFileWriter(EnvOptions(), options));
|
||||
ASSERT_OK(writer->Open(filename));
|
||||
for (int i = start_key; i < end_key; ++i) {
|
||||
auto kiter = kvs.find(Key(i));
|
||||
if (kiter != kvs.end()) {
|
||||
kvs.erase(kiter);
|
||||
}
|
||||
auto res =
|
||||
kvs.emplace(std::make_pair(Key(i), rnd->RandomString(2 << 10)));
|
||||
ASSERT_OK(writer->Put(res.first->first, res.first->second));
|
||||
}
|
||||
ASSERT_OK(writer->Finish());
|
||||
writer.reset();
|
||||
};
|
||||
|
||||
CreateColumnFamilies({"new_cf"}, options);
|
||||
std::string ingest_file = dbname_ + "test.sst";
|
||||
// Write ~200KB data
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_0", 0, 100);
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_1", 50, 51);
|
||||
ColumnFamilyHandle* cfh = handles_[0];
|
||||
IngestExternalFileOptions ifo;
|
||||
Status s = dbfull()->IngestExternalFile(
|
||||
cfh, {ingest_file + "_0", ingest_file + "_1"}, ifo);
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(Put(0, Key(50), rnd->RandomString(2 << 10)));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
{
|
||||
std::unique_ptr<SstFileWriter> writer;
|
||||
writer.reset(new SstFileWriter(EnvOptions(), options));
|
||||
ASSERT_OK(writer->Open(ingest_file + "_2"));
|
||||
ASSERT_OK(writer->DeleteRange("a", "z"));
|
||||
ASSERT_OK(writer->Finish());
|
||||
writer.reset();
|
||||
}
|
||||
create_ingestion_data_file_and_update_key_value(ingest_file + "_3", 0, 100);
|
||||
s = dbfull()->IngestExternalFile(
|
||||
cfh, {ingest_file + "_2", ingest_file + "_3"}, ifo);
|
||||
ASSERT_OK(s);
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// The first scan range overlaps the DB key range, while the second extends
|
||||
// beyond but overlaps the delete range
|
||||
std::vector<std::string> key_ranges({"key000085", "key000090", "l", "n"});
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = GetParam();
|
||||
MultiScanArgs scan_options(BytewiseComparator());
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
std::unique_ptr<MultiScan> iter =
|
||||
dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
int i = 0;
|
||||
int count = 0;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[i]);
|
||||
ASSERT_LT(it.first.ToString(), key_ranges[i + 1]);
|
||||
auto kiter = kvs.find(it.first.ToString());
|
||||
ASSERT_NE(kiter, kvs.end());
|
||||
ASSERT_EQ(kiter->second, it.second.ToString());
|
||||
count++;
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
ASSERT_EQ(i, 4);
|
||||
ASSERT_EQ(count, 5);
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_OK(ex.status());
|
||||
}
|
||||
iter.reset();
|
||||
|
||||
// The second scan range start overlaps the delete range in the first file
|
||||
// in Lmax-1, while the end overlaps the keys in the second file
|
||||
(*scan_options).clear();
|
||||
key_ranges[0] = "key000010";
|
||||
key_ranges[1] = "key000020";
|
||||
key_ranges[2] = "key0000500";
|
||||
key_ranges[3] = "key000060";
|
||||
scan_options.insert(key_ranges[0], key_ranges[1]);
|
||||
scan_options.insert(key_ranges[2], key_ranges[3]);
|
||||
iter = dbfull()->NewMultiScan(ro, cfh, scan_options);
|
||||
try {
|
||||
int i = 0;
|
||||
int count = 0;
|
||||
for (auto range : *iter) {
|
||||
for (auto it : range) {
|
||||
ASSERT_GE(it.first.ToString(), key_ranges[i]);
|
||||
ASSERT_LT(it.first.ToString(), key_ranges[i + 1]);
|
||||
auto kiter = kvs.find(it.first.ToString());
|
||||
ASSERT_NE(kiter, kvs.end());
|
||||
ASSERT_EQ(kiter->second, it.second.ToString());
|
||||
count++;
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
ASSERT_EQ(i, 4);
|
||||
ASSERT_EQ(count, 19);
|
||||
} catch (MultiScanException& ex) {
|
||||
ASSERT_OK(ex.status());
|
||||
}
|
||||
iter.reset();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -339,6 +339,135 @@ TEST_F(DBMemTableTest, ColumnFamilyId) {
|
||||
}
|
||||
}
|
||||
|
||||
class DBMemTableTestForSeek : public DBMemTableTest,
|
||||
virtual public ::testing::WithParamInterface<
|
||||
std::tuple<bool, bool, bool>> {};
|
||||
|
||||
TEST_P(DBMemTableTestForSeek, IntegrityChecks) {
|
||||
// Validate key corruption could be detected during seek.
|
||||
// We insert many keys into skiplist. Then we corrupt the each key one at a
|
||||
// time. With memtable_veirfy_per_key_checksum_on_seek enabled, when the
|
||||
// corrupted key is searched, the checksum of every key visited during the
|
||||
// seek is validated. It will report data corruption. Otherwise seek returns
|
||||
// not found.
|
||||
auto allow_data_in_error = std::get<0>(GetParam());
|
||||
Options options = CurrentOptions();
|
||||
options.allow_data_in_errors = allow_data_in_error;
|
||||
options.paranoid_memory_checks = std::get<1>(GetParam());
|
||||
options.memtable_veirfy_per_key_checksum_on_seek = std::get<2>(GetParam());
|
||||
options.memtable_protection_bytes_per_key = 8;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// capture the data pointer of all of the keys
|
||||
std::vector<char*> raw_data_pointer;
|
||||
|
||||
// Insert enough keys, so memtable would create multiple levels.
|
||||
auto key_count = 100;
|
||||
for (int i = 0; i < key_count; i++) {
|
||||
// The last digit of the key will be corrupted from value 0 to value 5
|
||||
ASSERT_OK(Put(Key(i * 10), "val0"));
|
||||
}
|
||||
|
||||
ReadOptions rops;
|
||||
|
||||
// Iterate all the keys to get key pointers
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->SetCallBack("InlineSkipList::Iterator::Next::key",
|
||||
[&raw_data_pointer](void* key) {
|
||||
auto p = static_cast<char*>(key);
|
||||
raw_data_pointer.push_back(p);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
{
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator(rops)};
|
||||
iter->Seek(Key(0));
|
||||
while (iter->Valid()) {
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Next();
|
||||
}
|
||||
// check status after valid returned false.
|
||||
auto status = iter->status();
|
||||
ASSERT_TRUE(status.ok());
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_EQ(raw_data_pointer.size(), key_count);
|
||||
|
||||
bool enable_key_validation_on_seek =
|
||||
options.memtable_veirfy_per_key_checksum_on_seek;
|
||||
|
||||
// For each key, corrupt it, validate corruption is detected correctly, then
|
||||
// revert it.
|
||||
for (int i = 0; i < key_count; i++) {
|
||||
std::string key_to_corrupt = Key(i * 10);
|
||||
raw_data_pointer[i][key_to_corrupt.size()] = '5';
|
||||
|
||||
auto corrupted_key = key_to_corrupt;
|
||||
corrupted_key.data()[key_to_corrupt.size() - 1] = '5';
|
||||
auto corrupted_key_slice =
|
||||
Slice(corrupted_key.data(), corrupted_key.length());
|
||||
auto corrupted_key_hex = corrupted_key_slice.ToString(/*hex=*/true);
|
||||
|
||||
{
|
||||
// Test Get API
|
||||
std::string val;
|
||||
auto status = db_->Get(rops, key_to_corrupt, &val);
|
||||
if (enable_key_validation_on_seek) {
|
||||
ASSERT_TRUE(status.IsCorruption()) << key_to_corrupt;
|
||||
ASSERT_EQ(
|
||||
status.ToString().find(corrupted_key_hex) != std::string::npos,
|
||||
allow_data_in_error)
|
||||
<< status.ToString() << "\n"
|
||||
<< corrupted_key_hex;
|
||||
} else {
|
||||
ASSERT_TRUE(status.IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Test MultiGet API
|
||||
std::vector<std::string> vals;
|
||||
std::vector<Status> statuses = db_->MultiGet(
|
||||
rops, {db_->DefaultColumnFamily()}, {key_to_corrupt}, &vals, nullptr);
|
||||
if (enable_key_validation_on_seek) {
|
||||
ASSERT_TRUE(statuses[0].IsCorruption());
|
||||
ASSERT_EQ(
|
||||
statuses[0].ToString().find(corrupted_key_hex) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
} else {
|
||||
ASSERT_TRUE(statuses[0].IsNotFound());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Test Iterator Seek API
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator(rops)};
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Seek(key_to_corrupt);
|
||||
auto status = iter->status();
|
||||
if (enable_key_validation_on_seek) {
|
||||
ASSERT_TRUE(status.IsCorruption());
|
||||
ASSERT_EQ(
|
||||
status.ToString().find(corrupted_key_hex) != std::string::npos,
|
||||
allow_data_in_error);
|
||||
} else {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_FALSE(status.ok());
|
||||
}
|
||||
}
|
||||
|
||||
// revert the key corruption.
|
||||
raw_data_pointer[i][key_to_corrupt.size()] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBMemTableTestForSeek, DBMemTableTestForSeek,
|
||||
::testing::Combine(::testing::Bool(), ::testing::Bool(),
|
||||
::testing::Bool()));
|
||||
|
||||
TEST_F(DBMemTableTest, IntegrityChecks) {
|
||||
// We insert keys key000000, key000001 and key000002 into skiplist at fixed
|
||||
// height 1 (smallest height). Then we corrupt the second key to aey000001 to
|
||||
|
||||
+3
-40
@@ -605,6 +605,9 @@ class TraceFileEnv : public EnvWrapper {
|
||||
char* scratch) const override {
|
||||
return target_->Read(offset, n, result, scratch);
|
||||
}
|
||||
Status GetFileSize(uint64_t* file_size) override {
|
||||
return target_->GetFileSize(file_size);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<RandomAccessFile> target_;
|
||||
@@ -1291,46 +1294,6 @@ TEST_F(DBSecondaryTest, CatchUpAfterFlush) {
|
||||
ASSERT_OK(iter3->status());
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, CheckConsistencyWhenOpen) {
|
||||
bool called = false;
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::CheckConsistency:AfterFirstAttempt", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
called = true;
|
||||
auto* s = static_cast<Status*>(arg);
|
||||
ASSERT_NOK(*s);
|
||||
});
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CheckConsistency:AfterGetLiveFilesMetaData",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"DBImpl::BackgroundCallCompaction:PurgedObsoleteFiles",
|
||||
"DBImpl::CheckConsistency:BeforeGetFileSize"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("a", "value0"));
|
||||
ASSERT_OK(Put("c", "value0"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("b", "value1"));
|
||||
ASSERT_OK(Put("d", "value1"));
|
||||
ASSERT_OK(Flush());
|
||||
port::Thread thread([this]() {
|
||||
Options opts;
|
||||
opts.env = env_;
|
||||
opts.max_open_files = -1;
|
||||
OpenSecondary(opts);
|
||||
});
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
thread.join();
|
||||
ASSERT_TRUE(called);
|
||||
}
|
||||
|
||||
TEST_F(DBSecondaryTest, StartFromInconsistent) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
+64
-54
@@ -135,21 +135,6 @@ TEST_F(DBSSTTest, SSTsWithLdbSuffixHandling) {
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
// Check that we don't crash when opening DB with
|
||||
// DBOptions::skip_checking_sst_file_sizes_on_db_open = true.
|
||||
TEST_F(DBSSTTest, SkipCheckingSSTFileSizesOnDBOpen) {
|
||||
ASSERT_OK(Put("pika", "choo"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Just open the DB with the option set to true and check that we don't crash.
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.skip_checking_sst_file_sizes_on_db_open = true;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ("choo", Get("pika"));
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DontDeleteMovedFile) {
|
||||
// This test triggers move compaction and verifies that the file is not
|
||||
// deleted when it's part of move compaction
|
||||
@@ -1748,45 +1733,6 @@ TEST_F(DBSSTTest, GetTotalSstFilesSize) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithoutGetFileSizeInvocations) {
|
||||
Options options = CurrentOptions();
|
||||
std::unique_ptr<MockEnv> env{MockEnv::Create(Env::Default())};
|
||||
options.env = env.get();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = 32; // create one blob per file
|
||||
options.skip_checking_sst_file_sizes_on_db_open = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
// Generate 5 files in L0
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
std::string val = "val_file_" + std::to_string(i);
|
||||
ASSERT_OK(Put(Key(j), val));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
Close();
|
||||
|
||||
bool is_get_file_size_called = false;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MockFileSystem::GetFileSize:CheckFileType", [&](void* arg) {
|
||||
std::string* filename = static_cast<std::string*>(arg);
|
||||
if (filename->find(".blob") != std::string::npos) {
|
||||
is_get_file_size_called = true;
|
||||
}
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Reopen(options);
|
||||
ASSERT_FALSE(is_get_file_size_called);
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, GetTotalSstFilesSizeVersionsFilesShared) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -1991,6 +1937,70 @@ TEST_F(DBSSTTest, DBWithSFMForBlobFilesAtomicFlush) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, SstGetFileSizeFails) {
|
||||
// Build an SST file
|
||||
ASSERT_OK(Put("x", "zaphod"));
|
||||
ASSERT_OK(Flush());
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
ASSERT_EQ(1U, metadata.size());
|
||||
std::string filename = dbname_ + metadata[0].name;
|
||||
|
||||
// Prepare for fault injection
|
||||
std::shared_ptr<FaultInjectionTestFS> fault_fs =
|
||||
std::make_shared<FaultInjectionTestFS>(
|
||||
CurrentOptions().env->GetFileSystem());
|
||||
std::unique_ptr<Env> fault_fs_env(NewCompositeEnv(fault_fs));
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_fs_env.get();
|
||||
options.paranoid_checks = false; // don't check file sizes on open
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
SCOPED_TRACE("Iteration = " + std::to_string(i));
|
||||
fault_fs->SetFailRandomAccessGetFileSizeSst(false);
|
||||
fault_fs->SetFailFilesystemGetFileSizeSst(false);
|
||||
Close();
|
||||
|
||||
if (i == 1) {
|
||||
// Just FSRandomAccessFile::GetFileSize fails, which should be worked
|
||||
// around
|
||||
fault_fs->SetFailRandomAccessGetFileSizeSst(true);
|
||||
} else if (i == 2) {
|
||||
// FileSystem::GetFileSize fails, which should be worked around if
|
||||
// FSRandomAccessFile::GetFileSize is supported
|
||||
fault_fs->SetFailFilesystemGetFileSizeSst(true);
|
||||
} else if (i == 3) {
|
||||
// Both GetFileSize APIs fail with an IOError
|
||||
fault_fs->SetFailRandomAccessGetFileSizeSst(true);
|
||||
fault_fs->SetFailFilesystemGetFileSizeSst(true);
|
||||
}
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
std::string value;
|
||||
Status get_status = db_->Get({}, "x", &value);
|
||||
if (i < 2) {
|
||||
ASSERT_OK(get_status);
|
||||
} else if (i == 2) {
|
||||
if (encrypted_env_) {
|
||||
// Can't recover because RandomAccessFile::GetFileSize is not supported
|
||||
// on EncryptedEnv
|
||||
// Fail with propagated IOError. (Not Corruption nor NotSupported!)
|
||||
ASSERT_EQ(get_status.code(), Status::Code::kIOError);
|
||||
ASSERT_STREQ(get_status.getState(), "FileSystem::GetFileSize failed");
|
||||
} else {
|
||||
// Never sees the FileSystem::GetFileSize failure
|
||||
ASSERT_OK(get_status);
|
||||
}
|
||||
} else {
|
||||
ASSERT_EQ(i, 3);
|
||||
// Fail with propagated IOError. (Not Corruption nor NotSupported!)
|
||||
ASSERT_EQ(get_status.code(), Status::Code::kIOError);
|
||||
ASSERT_STREQ(get_status.getState(), "FileSystem::GetFileSize failed");
|
||||
}
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -787,6 +787,46 @@ TEST_P(DBTablePropertiesTest, RatioBasedDeletionTriggeredCompactionMarking) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, KeyLargestSmallestSeqno) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key1", "value1"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key2", "value2"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "key3", "value3"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
|
||||
{
|
||||
TablePropertiesCollection props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
|
||||
ASSERT_EQ(1U, props.size());
|
||||
|
||||
auto table_props = props.begin()->second;
|
||||
|
||||
ASSERT_TRUE(table_props->HasKeyLargestSeqno());
|
||||
ASSERT_TRUE(table_props->HasKeySmallestSeqno());
|
||||
|
||||
ASSERT_EQ(table_props->key_largest_seqno,
|
||||
table_props->key_smallest_seqno + 2);
|
||||
ASSERT_GT(table_props->key_largest_seqno, 0U);
|
||||
ASSERT_GT(table_props->key_smallest_seqno, 0U);
|
||||
}
|
||||
|
||||
// Becomes zero after compaction
|
||||
{
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
TablePropertiesCollection props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(&props));
|
||||
ASSERT_EQ(1U, props.size());
|
||||
|
||||
auto table_props = props.begin()->second;
|
||||
ASSERT_TRUE(table_props->HasKeyLargestSeqno());
|
||||
ASSERT_TRUE(table_props->HasKeySmallestSeqno());
|
||||
|
||||
ASSERT_EQ(table_props->key_largest_seqno, table_props->key_smallest_seqno);
|
||||
ASSERT_EQ(table_props->key_largest_seqno, 0U);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBTablePropertiesTest, DBTablePropertiesTest,
|
||||
::testing::Values("kCompactionStyleLevel",
|
||||
"kCompactionStyleUniversal"));
|
||||
|
||||
-292
@@ -1278,12 +1278,6 @@ class DelayFilterFactory : public CompactionFilterFactory {
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
static std::string CompressibleString(Random* rnd, int len) {
|
||||
std::string r;
|
||||
test::CompressibleString(rnd, 0.8, len, &r);
|
||||
return r;
|
||||
}
|
||||
|
||||
TEST_F(DBTest, FailMoreDbPaths) {
|
||||
Options options = CurrentOptions();
|
||||
options.db_paths.emplace_back(dbname_, 10000000);
|
||||
@@ -5407,271 +5401,6 @@ TEST_F(DBTest, FlushOnDestroy) {
|
||||
CancelAllBackgroundWork(db_);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, DynamicLevelCompressionPerLevel) {
|
||||
if (!Snappy_Supported()) {
|
||||
return;
|
||||
}
|
||||
const int kNKeys = 120;
|
||||
int keys[kNKeys];
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
keys[i] = i;
|
||||
}
|
||||
|
||||
Random rnd(301);
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 20480;
|
||||
options.write_buffer_size = 20480;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.target_file_size_base = 20480;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 102400;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.max_background_compactions = 1;
|
||||
options.num_levels = 5;
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
options.compression_per_level.resize(3);
|
||||
// No compression for L0
|
||||
options.compression_per_level[0] = kNoCompression;
|
||||
// No compression for the Ln whre L0 is compacted to
|
||||
options.compression_per_level[1] = kNoCompression;
|
||||
// Snappy compression for Ln+1
|
||||
options.compression_per_level[2] = kSnappyCompression;
|
||||
|
||||
OnFileDeletionListener* listener = new OnFileDeletionListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Insert more than 80K. L4 should be base level. Neither L0 nor L4 should
|
||||
// be compressed, so there shouldn't be any compression.
|
||||
for (int i = 0; i < 20; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(3), 0);
|
||||
ASSERT_TRUE(NumTableFilesAtLevel(0) > 0 || NumTableFilesAtLevel(4) > 0);
|
||||
|
||||
// Verify there was no compression
|
||||
auto num_block_compressed =
|
||||
options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED);
|
||||
ASSERT_EQ(num_block_compressed, 0);
|
||||
|
||||
// Insert 400KB and there will be some files end up in L3. According to the
|
||||
// above compression settings for each level, there will be some compression.
|
||||
ASSERT_OK(options.statistics->Reset());
|
||||
ASSERT_EQ(num_block_compressed, 0);
|
||||
for (int i = 20; i < 120; i++) {
|
||||
ASSERT_OK(Put(Key(keys[i]), CompressibleString(&rnd, 4000)));
|
||||
ASSERT_OK(dbfull()->TEST_WaitForBackgroundWork());
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_GE(NumTableFilesAtLevel(3), 1);
|
||||
ASSERT_GE(NumTableFilesAtLevel(4), 1);
|
||||
|
||||
// Verify there was compression
|
||||
num_block_compressed =
|
||||
options.statistics->getTickerCount(NUMBER_BLOCK_COMPRESSED);
|
||||
ASSERT_GT(num_block_compressed, 0);
|
||||
|
||||
// Make sure data in files in L3 is not compacted by removing all files
|
||||
// in L4 and calculate number of rows
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
|
||||
// Ensure that L1+ files are non-overlapping and together with L0 encompass
|
||||
// full key range between smallestkey and largestkey from CF file metadata.
|
||||
int largestkey_in_prev_level = -1;
|
||||
int keys_found = 0;
|
||||
for (int level = (int)cf_meta.levels.size() - 1; level >= 0; level--) {
|
||||
int files_in_level = (int)cf_meta.levels[level].files.size();
|
||||
int largestkey_in_prev_file = -1;
|
||||
for (int j = 0; j < files_in_level; j++) {
|
||||
int smallestkey = IdFromKey(cf_meta.levels[level].files[j].smallestkey);
|
||||
int largestkey = IdFromKey(cf_meta.levels[level].files[j].largestkey);
|
||||
int num_entries = (int)cf_meta.levels[level].files[j].num_entries;
|
||||
ASSERT_EQ(num_entries, largestkey - smallestkey + 1);
|
||||
keys_found += num_entries;
|
||||
if (level > 0) {
|
||||
if (j == 0) {
|
||||
ASSERT_GT(smallestkey, largestkey_in_prev_level);
|
||||
}
|
||||
if (j > 0) {
|
||||
ASSERT_GT(smallestkey, largestkey_in_prev_file);
|
||||
}
|
||||
if (j == files_in_level - 1) {
|
||||
largestkey_in_prev_level = largestkey;
|
||||
}
|
||||
}
|
||||
largestkey_in_prev_file = largestkey;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(keys_found, kNKeys);
|
||||
|
||||
for (const auto& file : cf_meta.levels[4].files) {
|
||||
listener->SetExpectedFileName(dbname_ + file.name);
|
||||
const RangeOpt ranges(file.smallestkey, file.largestkey);
|
||||
// Given verification from above, we're guaranteed that by deleting all the
|
||||
// files in [<smallestkey>, <largestkey>] range, we're effectively deleting
|
||||
// that very single file and nothing more.
|
||||
EXPECT_OK(dbfull()->DeleteFilesInRanges(dbfull()->DefaultColumnFamily(),
|
||||
&ranges, true /* include_end */));
|
||||
}
|
||||
listener->VerifyMatchedCount(cf_meta.levels[4].files.size());
|
||||
|
||||
int num_keys = 0;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
num_keys++;
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_GE(NumTableFilesAtLevel(3), 1);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(4), 0);
|
||||
|
||||
ASSERT_GT(SizeAtLevel(0) + SizeAtLevel(3), num_keys * 4000U + num_keys * 10U);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, DynamicLevelCompressionPerLevel2) {
|
||||
if (!Snappy_Supported() || !LZ4_Supported() || !Zlib_Supported()) {
|
||||
return;
|
||||
}
|
||||
const int kNKeys = 500;
|
||||
int keys[kNKeys];
|
||||
for (int i = 0; i < kNKeys; i++) {
|
||||
keys[i] = i;
|
||||
}
|
||||
RandomShuffle(std::begin(keys), std::end(keys));
|
||||
|
||||
Random rnd(301);
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 6000000;
|
||||
options.write_buffer_size = 600000;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.soft_pending_compaction_bytes_limit = 1024 * 1024;
|
||||
options.target_file_size_base = 20;
|
||||
options.env = env_;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 200;
|
||||
options.max_bytes_for_level_multiplier = 8;
|
||||
options.max_background_compactions = 1;
|
||||
options.num_levels = 5;
|
||||
std::shared_ptr<mock::MockTableFactory> mtf(new mock::MockTableFactory);
|
||||
options.table_factory = mtf;
|
||||
|
||||
options.compression_per_level.resize(3);
|
||||
options.compression_per_level[0] = kNoCompression;
|
||||
options.compression_per_level[1] = kLZ4Compression;
|
||||
options.compression_per_level[2] = kZlibCompression;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
// When base level is L4, L4 is LZ4.
|
||||
std::atomic<int> num_zlib(0);
|
||||
std::atomic<int> num_lz4(0);
|
||||
std::atomic<int> num_no(0);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = static_cast<Compaction*>(arg);
|
||||
if (compaction->output_level() == 4) {
|
||||
ASSERT_TRUE(compaction->output_compression() == kLZ4Compression);
|
||||
num_lz4.fetch_add(1);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:output_compression", [&](void* arg) {
|
||||
auto* compression = static_cast<CompressionType*>(arg);
|
||||
ASSERT_TRUE(*compression == kNoCompression);
|
||||
num_no.fetch_add(1);
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::string value = rnd.RandomString(200);
|
||||
ASSERT_OK(Put(Key(keys[i]), value));
|
||||
if (i % 25 == 24) {
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(3), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(4), 0);
|
||||
ASSERT_GT(num_no.load(), 2);
|
||||
ASSERT_GT(num_lz4.load(), 0);
|
||||
int prev_num_files_l4 = NumTableFilesAtLevel(4);
|
||||
|
||||
// After base level turn L4->L3, L3 becomes LZ4 and L4 becomes Zlib
|
||||
num_lz4.store(0);
|
||||
num_no.store(0);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"LevelCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
Compaction* compaction = static_cast<Compaction*>(arg);
|
||||
if (compaction->output_level() == 4 && compaction->start_level() == 3) {
|
||||
ASSERT_TRUE(compaction->output_compression() == kZlibCompression);
|
||||
num_zlib.fetch_add(1);
|
||||
} else {
|
||||
ASSERT_TRUE(compaction->output_compression() == kLZ4Compression);
|
||||
num_lz4.fetch_add(1);
|
||||
}
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"FlushJob::WriteLevel0Table:output_compression", [&](void* arg) {
|
||||
auto* compression = static_cast<CompressionType*>(arg);
|
||||
ASSERT_TRUE(*compression == kNoCompression);
|
||||
num_no.fetch_add(1);
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (int i = 101; i < 500; i++) {
|
||||
std::string value = rnd.RandomString(200);
|
||||
ASSERT_OK(Put(Key(keys[i]), value));
|
||||
if (i % 100 == 99) {
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(2), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(3), 0);
|
||||
ASSERT_GT(NumTableFilesAtLevel(4), prev_num_files_l4);
|
||||
ASSERT_GT(num_no.load(), 2);
|
||||
ASSERT_GT(num_lz4.load(), 0);
|
||||
ASSERT_GT(num_zlib.load(), 0);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, DynamicCompactionOptions) {
|
||||
// minimum write buffer size is enforced at 64KB
|
||||
const uint64_t k32KB = 1 << 15;
|
||||
@@ -7349,27 +7078,6 @@ TEST_F(DBTest, LastWriteBufferDelay) {
|
||||
}
|
||||
#endif // !defined(ROCKSDB_DISABLE_STALL_NOTIFICATION)
|
||||
|
||||
TEST_F(DBTest, FailWhenCompressionNotSupportedTest) {
|
||||
CompressionType compressions[] = {kZlibCompression, kBZip2Compression,
|
||||
kLZ4Compression, kLZ4HCCompression,
|
||||
kXpressCompression};
|
||||
for (auto comp : compressions) {
|
||||
if (!CompressionTypeSupported(comp)) {
|
||||
// not supported, we should fail the Open()
|
||||
Options options = CurrentOptions();
|
||||
options.compression = comp;
|
||||
ASSERT_TRUE(!TryReopen(options).ok());
|
||||
// Try if CreateColumnFamily also fails
|
||||
options.compression = kNoCompression;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
ColumnFamilyOptions cf_options(options);
|
||||
cf_options.compression = comp;
|
||||
ColumnFamilyHandle* handle;
|
||||
ASSERT_TRUE(!db_->CreateColumnFamily(cf_options, "name", &handle).ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTest, CreateColumnFamilyShouldFailOnIncompatibleOptions) {
|
||||
Options options = CurrentOptions();
|
||||
options.max_open_files = 100;
|
||||
|
||||
+25
-1250
File diff suppressed because it is too large.
Load diff
+42
-24
@@ -71,9 +71,9 @@ DBTestBase::DBTestBase(const std::string path, bool env_do_fsync)
|
||||
if (getenv("MEM_ENV")) {
|
||||
mem_env_ = MockEnv::Create(base_env, base_env->GetSystemClock());
|
||||
}
|
||||
if (getenv("ENCRYPTED_ENV")) {
|
||||
if (auto ee = getenv("ENCRYPTED_ENV")) {
|
||||
std::shared_ptr<EncryptionProvider> provider;
|
||||
std::string provider_id = getenv("ENCRYPTED_ENV");
|
||||
std::string provider_id = ee;
|
||||
if (provider_id.find('=') == std::string::npos &&
|
||||
!EndsWith(provider_id, "://test")) {
|
||||
provider_id = provider_id + "://test";
|
||||
@@ -366,11 +366,6 @@ Options DBTestBase::GetOptions(
|
||||
table_options.block_cache = NewLRUCache(/* too small */ 1);
|
||||
}
|
||||
|
||||
// Test anticipated new default as much as reasonably possible (and remove
|
||||
// this code when obsolete)
|
||||
assert(!table_options.decouple_partitioned_filters);
|
||||
table_options.decouple_partitioned_filters = true;
|
||||
|
||||
bool can_allow_mmap = IsMemoryMappedAccessSupported();
|
||||
switch (option_config) {
|
||||
case kHashSkipList:
|
||||
@@ -1159,16 +1154,18 @@ size_t DBTestBase::CountLiveFiles() {
|
||||
}
|
||||
|
||||
int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
|
||||
std::string property;
|
||||
if (cf == 0) {
|
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.num-files-at-level" + std::to_string(level), &property));
|
||||
} else {
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
handles_[cf], "rocksdb.num-files-at-level" + std::to_string(level),
|
||||
&property));
|
||||
return NumTableFilesAtLevel(level,
|
||||
cf ? handles_[cf] : db_->DefaultColumnFamily());
|
||||
}
|
||||
|
||||
int DBTestBase::NumTableFilesAtLevel(int level, ColumnFamilyHandle* cfh,
|
||||
DB* db) {
|
||||
if (!db) {
|
||||
db = db_;
|
||||
}
|
||||
std::string property;
|
||||
EXPECT_TRUE(db->GetProperty(
|
||||
cfh, "rocksdb.num-files-at-level" + std::to_string(level), &property));
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
|
||||
@@ -1201,12 +1198,22 @@ int DBTestBase::TotalTableFiles(int cf, int levels) {
|
||||
|
||||
// Return spread of files per level
|
||||
std::string DBTestBase::FilesPerLevel(int cf) {
|
||||
int num_levels =
|
||||
(cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[cf]);
|
||||
if (cf == 0) {
|
||||
return FilesPerLevel(db_->DefaultColumnFamily());
|
||||
} else {
|
||||
return FilesPerLevel(handles_[cf]);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DBTestBase::FilesPerLevel(ColumnFamilyHandle* cfh, DB* db) {
|
||||
if (!db) {
|
||||
db = db_;
|
||||
}
|
||||
int num_levels = db->NumberLevels(cfh);
|
||||
std::string result;
|
||||
size_t last_non_zero_offset = 0;
|
||||
for (int level = 0; level < num_levels; level++) {
|
||||
int f = NumTableFilesAtLevel(level, cf);
|
||||
int f = NumTableFilesAtLevel(level, cfh, db);
|
||||
char buf[100];
|
||||
snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f);
|
||||
result += buf;
|
||||
@@ -1339,12 +1346,14 @@ void DBTestBase::FillLevels(const std::string& smallest,
|
||||
}
|
||||
|
||||
void DBTestBase::MoveFilesToLevel(int level, int cf) {
|
||||
MoveFilesToLevel(level, cf ? handles_[cf] : db_->DefaultColumnFamily());
|
||||
}
|
||||
|
||||
void DBTestBase::MoveFilesToLevel(int level, ColumnFamilyHandle* column_family,
|
||||
DB* db) {
|
||||
DBImpl* db_impl = db ? static_cast<DBImpl*>(db) : dbfull();
|
||||
for (int l = 0; l < level; ++l) {
|
||||
if (cf > 0) {
|
||||
EXPECT_OK(dbfull()->TEST_CompactRange(l, nullptr, nullptr, handles_[cf]));
|
||||
} else {
|
||||
EXPECT_OK(dbfull()->TEST_CompactRange(l, nullptr, nullptr));
|
||||
}
|
||||
EXPECT_OK(db_impl->TEST_CompactRange(l, nullptr, nullptr, column_family));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1863,4 +1872,13 @@ template class TargetCacheChargeTrackingCache<
|
||||
CacheEntryRole::kBlockBasedTableReader>;
|
||||
template class TargetCacheChargeTrackingCache<CacheEntryRole::kFileMetadata>;
|
||||
|
||||
const std::vector<Temperature> kKnownTemperatures = {
|
||||
Temperature::kHot, Temperature::kWarm, Temperature::kCool,
|
||||
Temperature::kCold, Temperature::kIce};
|
||||
|
||||
Temperature RandomKnownTemperature() {
|
||||
return kKnownTemperatures[Random::GetTLSInstance()->Uniform(
|
||||
static_cast<int>(kKnownTemperatures.size()))];
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+37
-14
@@ -452,6 +452,10 @@ class SpecialEnv : public EnvWrapper {
|
||||
return s;
|
||||
}
|
||||
|
||||
Status GetFileSize(uint64_t* s) override {
|
||||
return target_->GetFileSize(s);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<RandomAccessFile> target_;
|
||||
anon::AtomicCounter* counter_;
|
||||
@@ -478,6 +482,10 @@ class SpecialEnv : public EnvWrapper {
|
||||
return target_->Prefetch(offset, n);
|
||||
}
|
||||
|
||||
Status GetFileSize(uint64_t* s) override {
|
||||
return target_->GetFileSize(s);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<RandomAccessFile> target_;
|
||||
std::atomic<uint64_t>* fail_cnt_;
|
||||
@@ -1272,6 +1280,9 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
int NumTableFilesAtLevel(int level, int cf = 0);
|
||||
|
||||
int NumTableFilesAtLevel(int level, ColumnFamilyHandle* column_family,
|
||||
DB* db = nullptr);
|
||||
|
||||
double CompressionRatioAtLevel(int level, int cf = 0);
|
||||
|
||||
int TotalTableFiles(int cf = 0, int levels = -1);
|
||||
@@ -1281,6 +1292,8 @@ class DBTestBase : public testing::Test {
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel(int cf = 0);
|
||||
|
||||
std::string FilesPerLevel(ColumnFamilyHandle* cfh, DB* db = nullptr);
|
||||
|
||||
size_t CountFiles();
|
||||
|
||||
Status CountFiles(size_t* count);
|
||||
@@ -1312,6 +1325,9 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
void MoveFilesToLevel(int level, int cf = 0);
|
||||
|
||||
void MoveFilesToLevel(int level, ColumnFamilyHandle* column_family,
|
||||
DB* db = nullptr);
|
||||
|
||||
void DumpFileCounts(const char* label);
|
||||
|
||||
std::string DumpSSTableList();
|
||||
@@ -1422,20 +1438,23 @@ class DBTestBase : public testing::Test {
|
||||
std::replace(tp_string.begin(), tp_string.end(), ';', ' ');
|
||||
std::replace(tp_string.begin(), tp_string.end(), '=', ' ');
|
||||
ResetTableProperties(tp);
|
||||
sscanf(tp_string.c_str(),
|
||||
"# data blocks %" SCNu64 " # entries %" SCNu64
|
||||
" # deletions %" SCNu64 " # merge operands %" SCNu64
|
||||
" # range deletions %" SCNu64 " raw key size %" SCNu64
|
||||
" raw average key size %lf "
|
||||
" raw value size %" SCNu64
|
||||
" raw average value size %lf "
|
||||
" data block size %" SCNu64 " index block size (user-key? %" SCNu64
|
||||
", delta-value? %" SCNu64 ") %" SCNu64 " filter block size %" SCNu64,
|
||||
&tp->num_data_blocks, &tp->num_entries, &tp->num_deletions,
|
||||
&tp->num_merge_operands, &tp->num_range_deletions, &tp->raw_key_size,
|
||||
&dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size,
|
||||
&tp->index_key_is_user_key, &tp->index_value_is_delta_encoded,
|
||||
&tp->index_size, &tp->filter_size);
|
||||
int count = sscanf(
|
||||
tp_string.c_str(),
|
||||
"# data blocks %" SCNu64 " # entries %" SCNu64 " # deletions %" SCNu64
|
||||
" # merge operands %" SCNu64 " # range deletions %" SCNu64
|
||||
" raw key size %" SCNu64
|
||||
" raw average key size %lf "
|
||||
" raw value size %" SCNu64
|
||||
" raw average value size %lf "
|
||||
" data block size %" SCNu64 " data uncompressed size %" SCNu64
|
||||
" index block size (user-key? %" SCNu64 ", delta-value? %" SCNu64
|
||||
") %" SCNu64 " filter block size %" SCNu64,
|
||||
&tp->num_data_blocks, &tp->num_entries, &tp->num_deletions,
|
||||
&tp->num_merge_operands, &tp->num_range_deletions, &tp->raw_key_size,
|
||||
&dummy_double, &tp->raw_value_size, &dummy_double, &tp->data_size,
|
||||
&tp->uncompressed_data_size, &tp->index_key_is_user_key,
|
||||
&tp->index_value_is_delta_encoded, &tp->index_size, &tp->filter_size);
|
||||
ASSERT_EQ(count, 15);
|
||||
}
|
||||
|
||||
private: // Prone to error on direct use
|
||||
@@ -1448,4 +1467,8 @@ class DBTestBase : public testing::Test {
|
||||
// unique ids.
|
||||
void VerifySstUniqueIds(const TablePropertiesCollection& props);
|
||||
|
||||
// Excludes kUnknown
|
||||
extern const std::vector<Temperature> kKnownTemperatures;
|
||||
Temperature RandomKnownTemperature();
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -2106,46 +2106,79 @@ TEST_F(DBTestUniversalCompaction2, OverlappingL0) {
|
||||
}
|
||||
|
||||
TEST_F(DBTestUniversalCompaction2, IngestBehind) {
|
||||
const int kNumKeys = 3000;
|
||||
const int kWindowSize = 100;
|
||||
const int kNumDelsTrigger = 90;
|
||||
for (bool cf_option : {false, true}) {
|
||||
SCOPED_TRACE("cf_option = " + std::to_string(cf_option));
|
||||
const int kNumKeys = 3000;
|
||||
const int kWindowSize = 100;
|
||||
const int kNumDelsTrigger = 90;
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.table_properties_collector_factories.emplace_back(
|
||||
NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger));
|
||||
opts.compaction_style = kCompactionStyleUniversal;
|
||||
opts.level0_file_num_compaction_trigger = 2;
|
||||
opts.compression = kNoCompression;
|
||||
opts.allow_ingest_behind = true;
|
||||
opts.compaction_options_universal.size_ratio = 10;
|
||||
opts.compaction_options_universal.min_merge_width = 2;
|
||||
opts.compaction_options_universal.max_size_amplification_percent = 200;
|
||||
Reopen(opts);
|
||||
|
||||
// add an L1 file to prevent tombstones from dropping due to obsolescence
|
||||
// during flush
|
||||
int i;
|
||||
for (i = 0; i < 2000; ++i) {
|
||||
ASSERT_OK(Put(Key(i), "val"));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
// MoveFilesToLevel(6);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
for (i = 1999; i < kNumKeys; ++i) {
|
||||
if (i >= kNumKeys - kWindowSize &&
|
||||
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
Options opts = CurrentOptions();
|
||||
opts.table_properties_collector_factories.emplace_back(
|
||||
NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger));
|
||||
opts.compaction_style = kCompactionStyleUniversal;
|
||||
opts.level0_file_num_compaction_trigger = 2;
|
||||
opts.compression = kNoCompression;
|
||||
if (cf_option) {
|
||||
opts.cf_allow_ingest_behind = true;
|
||||
} else {
|
||||
opts.allow_ingest_behind = true;
|
||||
}
|
||||
opts.compaction_options_universal.size_ratio = 10;
|
||||
opts.compaction_options_universal.min_merge_width = 2;
|
||||
opts.compaction_options_universal.max_size_amplification_percent = 200;
|
||||
Reopen(opts);
|
||||
|
||||
// add an L1 file to prevent tombstones from dropping due to obsolescence
|
||||
// during flush
|
||||
int i;
|
||||
for (i = 0; i < 2000; ++i) {
|
||||
ASSERT_OK(Put(Key(i), "val"));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Flush());
|
||||
// MoveFilesToLevel(6);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(6));
|
||||
ASSERT_GT(NumTableFilesAtLevel(5), 0);
|
||||
for (i = 1999; i < kNumKeys; ++i) {
|
||||
if (i >= kNumKeys - kWindowSize &&
|
||||
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
} else {
|
||||
ASSERT_OK(Put(Key(i), "val"));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(6));
|
||||
ASSERT_GT(NumTableFilesAtLevel(5), 0);
|
||||
|
||||
if (cf_option) {
|
||||
// Test that another CF does not allow ingest behind
|
||||
ColumnFamilyHandle* new_cfh;
|
||||
Options new_cf_option;
|
||||
new_cf_option.compaction_style = kCompactionStyleUniversal;
|
||||
new_cf_option.num_levels = 7;
|
||||
// CreateColumnFamilies({"new_cf"}, new_cf_option);
|
||||
ASSERT_OK(db_->CreateColumnFamily(new_cf_option, "new_cf", &new_cfh));
|
||||
// handles_.push_back(new_cfh);
|
||||
for (i = 0; i < 10; ++i) {
|
||||
// ASSERT_OK(Put(1, Key(i), "val"));
|
||||
ASSERT_OK(db_->Put(WriteOptions(), new_cfh, Key(i), "val"));
|
||||
}
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(CompactRangeOptions(), new_cfh, nullptr, nullptr));
|
||||
// This CF can use the last leve.
|
||||
std::string property;
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
new_cfh, "rocksdb.num-files-at-level" + std::to_string(6),
|
||||
&property));
|
||||
ASSERT_EQ(1, atoi(property.c_str()));
|
||||
|
||||
ASSERT_OK(db_->DropColumnFamily(new_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(new_cfh));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestUniversalCompaction2, PeriodicCompactionDefault) {
|
||||
|
||||
@@ -2400,7 +2400,6 @@ class DataVisibilityTest : public DBBasicTestWithTimestampBase {
|
||||
}
|
||||
}
|
||||
};
|
||||
constexpr int DataVisibilityTest::kTestDataSize;
|
||||
|
||||
// Application specifies timestamp but not snapshot.
|
||||
// reader writer
|
||||
|
||||
@@ -129,6 +129,7 @@ void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
<< "user_defined_timestamps_persisted"
|
||||
<< table_properties.user_defined_timestamps_persisted
|
||||
<< "key_largest_seqno" << table_properties.key_largest_seqno
|
||||
<< "key_smallest_seqno" << table_properties.key_smallest_seqno
|
||||
<< "merge_operator" << table_properties.merge_operator_name
|
||||
<< "prefix_extractor_name"
|
||||
<< table_properties.prefix_extractor_name << "property_collectors"
|
||||
|
||||
+5
-4
@@ -88,11 +88,12 @@ Status GetFileChecksumsFromCurrentManifest(FileSystem* fs,
|
||||
|
||||
// Read all records from the manifest file...
|
||||
uint64_t manifest_file_size = std::numeric_limits<uint64_t>::max();
|
||||
FileChecksumRetriever retriever(read_options, manifest_file_size,
|
||||
*checksum_list);
|
||||
FileChecksumRetriever retriever(read_options, manifest_file_size);
|
||||
retriever.Iterate(reader, &s);
|
||||
|
||||
return retriever.status();
|
||||
if (!retriever.status().ok()) {
|
||||
return retriever.status();
|
||||
}
|
||||
return retriever.FetchFileChecksumList(*checksum_list);
|
||||
}
|
||||
|
||||
Status UpdateManifestForFilesState(
|
||||
|
||||
@@ -2567,7 +2567,14 @@ TEST_F(ExternalSSTFileBasicTest, IngestWithTemperature) {
|
||||
options.default_write_temperature = Temperature::kHot;
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.allow_ingest_behind = (mode == "ingest_behind");
|
||||
bool cf_option = Random::GetTLSInstance()->OneIn(2);
|
||||
SCOPED_TRACE(std::string("Use ") + (cf_option ? "CF" : "DB") +
|
||||
" option for ingest behind");
|
||||
if (cf_option) {
|
||||
options.cf_allow_ingest_behind = (mode == "ingest_behind");
|
||||
} else {
|
||||
options.allow_ingest_behind = (mode == "ingest_behind");
|
||||
}
|
||||
Reopen(options);
|
||||
Defer destroyer([&]() { Destroy(options); });
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
status =
|
||||
GetIngestedFileInfo(file_path, next_file_number++, &file_to_ingest, sv);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to get ingested file info: %s: %s",
|
||||
file_path.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -122,24 +125,28 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
}
|
||||
}
|
||||
|
||||
if (ingestion_options_.ingest_behind && files_overlap_) {
|
||||
return Status::NotSupported(
|
||||
"Files with overlapping ranges cannot be ingested with ingestion "
|
||||
"behind mode.");
|
||||
}
|
||||
if (files_overlap_) {
|
||||
if (ingestion_options_.ingest_behind) {
|
||||
return Status::NotSupported(
|
||||
"Files with overlapping ranges cannot be ingested with ingestion "
|
||||
"behind mode.");
|
||||
}
|
||||
|
||||
// Overlapping files need at least two different sequence numbers. If settings
|
||||
// disables global seqno, ingestion will fail anyway, so fail fast in prepare.
|
||||
if (!ingestion_options_.allow_global_seqno && files_overlap_) {
|
||||
return Status::InvalidArgument(
|
||||
"Global seqno is required, but disabled (because external files key "
|
||||
"range overlaps).");
|
||||
}
|
||||
// Overlapping files need at least two different sequence numbers. If
|
||||
// settings disables global seqno, ingestion will fail anyway, so fail
|
||||
// fast in prepare.
|
||||
if (!ingestion_options_.allow_global_seqno &&
|
||||
!ingestion_options_.allow_db_generated_files) {
|
||||
return Status::InvalidArgument(
|
||||
"Global seqno is required, but disabled (because external files key "
|
||||
"range overlaps).");
|
||||
}
|
||||
|
||||
if (ucmp_->timestamp_size() > 0 && files_overlap_) {
|
||||
return Status::NotSupported(
|
||||
"Files with overlapping ranges cannot be ingested to column "
|
||||
"family with user-defined timestamp enabled.");
|
||||
if (ucmp_->timestamp_size() > 0) {
|
||||
return Status::NotSupported(
|
||||
"Files with overlapping ranges cannot be ingested to column "
|
||||
"family with user-defined timestamp enabled.");
|
||||
}
|
||||
}
|
||||
|
||||
// Copy/Move external files into DB
|
||||
@@ -156,6 +163,14 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
// It is unsafe to assume application had sync the file and file
|
||||
// directory before ingest the file. For integrity of RocksDB we need
|
||||
// to sync the file.
|
||||
|
||||
// TODO(xingbo), We should in general be moving away from production
|
||||
// uses of ReuseWritableFile (except explicitly for WAL recycling),
|
||||
// ReopenWritableFile, and NewRandomRWFile. We should create a
|
||||
// FileSystem::SyncFile/FsyncFile API that by default does the
|
||||
// re-open+sync+close combo but can (a) be reused easily, and (b) be
|
||||
// overridden to do that more cleanly, e.g. in EncryptedEnv.
|
||||
// https://github.com/facebook/rocksdb/issues/13741
|
||||
std::unique_ptr<FSWritableFile> file_to_sync;
|
||||
Status s = fs_->ReopenWritableFile(path_inside_db, env_options_,
|
||||
&file_to_sync, nullptr);
|
||||
@@ -186,6 +201,10 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"Tried to link file %s but it's not supported : %s",
|
||||
path_outside_db.c_str(), status.ToString().c_str());
|
||||
} else {
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Failed to link file %s to %s: %s",
|
||||
path_outside_db.c_str(), path_inside_db.c_str(),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
} else {
|
||||
f.copy_file = true;
|
||||
@@ -210,6 +229,12 @@ Status ExternalSstFileIngestionJob::Prepare(
|
||||
io_tracer_);
|
||||
// The destination of the copy will be ingested
|
||||
f.file_temperature = dst_temp;
|
||||
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Failed to copy file %s to %s: %s",
|
||||
path_outside_db.c_str(), path_inside_db.c_str(),
|
||||
status.ToString().c_str());
|
||||
}
|
||||
} else {
|
||||
// Note: we currently assume that linking files does not cross
|
||||
// temperatures, so no need to change f.file_temperature
|
||||
@@ -435,6 +460,11 @@ Status ExternalSstFileIngestionJob::NeedsFlush(bool* flush_needed,
|
||||
}
|
||||
status = cfd_->RangesOverlapWithMemtables(
|
||||
ranges, super_version, db_options_.allow_data_in_errors, flush_needed);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to check ranges overlap with memtables: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
}
|
||||
if (status.ok() && *flush_needed) {
|
||||
if (!ingestion_options_.allow_blocking_flush) {
|
||||
@@ -469,6 +499,9 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
bool need_flush = false;
|
||||
status = NeedsFlush(&need_flush, super_version);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to check if flush is needed: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
if (need_flush) {
|
||||
@@ -532,12 +565,17 @@ Status ExternalSstFileIngestionJob::Run() {
|
||||
|
||||
// Find levels to ingest into
|
||||
std::optional<int> prev_batch_uppermost_level;
|
||||
// batches at the front of file_batches_to_ingest_ contains older updates and
|
||||
// are placed in smaller levels.
|
||||
for (auto& batch : file_batches_to_ingest_) {
|
||||
int batch_uppermost_level = 0;
|
||||
status = AssignLevelsForOneBatch(batch, super_version, force_global_seqno,
|
||||
&last_seqno, &batch_uppermost_level,
|
||||
prev_batch_uppermost_level);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to assign levels for one batch: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -580,8 +618,19 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
&largest_parsed, false /* log_err_key */);
|
||||
}
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log, "Failed to parse internal key: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
// If any ingested file overlaps with the DB, it will fail here.
|
||||
if (ingestion_options_.allow_db_generated_files && assigned_seqno != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"An ingested file overlaps with existing data in the DB and has been "
|
||||
"assigned a non-zero sequence number, which is not allowed when "
|
||||
"'allow_db_generated_files' is enabled.");
|
||||
}
|
||||
|
||||
if (smallest_parsed.sequence == 0 && assigned_seqno != 0) {
|
||||
UpdateInternalKey(file->smallest_internal_key.rep(), assigned_seqno,
|
||||
smallest_parsed.type);
|
||||
@@ -593,6 +642,10 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
|
||||
status = AssignGlobalSeqnoForIngestedFile(file, assigned_seqno);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to assign global sequence number for ingested file: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("ExternalSstFileIngestionJob::Run",
|
||||
@@ -600,11 +653,14 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
assert(assigned_seqno == 0 || assigned_seqno == *last_seqno + 1);
|
||||
if (assigned_seqno > *last_seqno) {
|
||||
*last_seqno = assigned_seqno;
|
||||
++consumed_seqno_count_;
|
||||
}
|
||||
max_assigned_seqno_ = std::max(max_assigned_seqno_, assigned_seqno);
|
||||
|
||||
status = GenerateChecksumForIngestedFile(file);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to generate checksum for ingested file: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -624,15 +680,24 @@ Status ExternalSstFileIngestionJob::AssignLevelsForOneBatch(
|
||||
file->table_properties.num_range_deletions == 1 &&
|
||||
(file->table_properties.num_entries ==
|
||||
file->table_properties.num_range_deletions);
|
||||
SequenceNumber smallest_seqno = file->assigned_seqno;
|
||||
SequenceNumber largest_seqno = file->assigned_seqno;
|
||||
if (ingestion_options_.allow_db_generated_files) {
|
||||
assert(file->assigned_seqno == 0);
|
||||
assert(file->smallest_seqno != kMaxSequenceNumber);
|
||||
assert(file->largest_seqno != kMaxSequenceNumber);
|
||||
smallest_seqno = file->smallest_seqno;
|
||||
largest_seqno = file->largest_seqno;
|
||||
max_assigned_seqno_ = std::max(max_assigned_seqno_, file->largest_seqno);
|
||||
}
|
||||
FileMetaData f_metadata(
|
||||
file->fd.GetNumber(), file->fd.GetPathId(), file->fd.GetFileSize(),
|
||||
file->smallest_internal_key, file->largest_internal_key,
|
||||
file->assigned_seqno, file->assigned_seqno, false,
|
||||
file->file_temperature, kInvalidBlobFileNumber, oldest_ancester_time,
|
||||
current_time,
|
||||
file->smallest_internal_key, file->largest_internal_key, smallest_seqno,
|
||||
largest_seqno, false, file->file_temperature, kInvalidBlobFileNumber,
|
||||
oldest_ancester_time, current_time,
|
||||
ingestion_options_.ingest_behind
|
||||
? kReservedEpochNumberForFileIngestedBehind
|
||||
: cfd_->NewEpochNumber(),
|
||||
: cfd_->NewEpochNumber(), // orders files ingested to L0
|
||||
file->file_checksum, file->file_checksum_func_name, file->unique_id, 0,
|
||||
tail_size, file->user_defined_timestamps_persisted);
|
||||
f_metadata.temperature = file->file_temperature;
|
||||
@@ -689,10 +754,9 @@ void ExternalSstFileIngestionJob::CreateEquivalentFileIngestingCompactions() {
|
||||
0 /* max_subcompaction, not applicable */,
|
||||
{} /* grandparents, not applicable */,
|
||||
std::nullopt /* earliest_snapshot */, nullptr /* snapshot_checker */,
|
||||
false /* is manual */, "" /* trim_ts */, -1 /* score, not applicable */,
|
||||
false /* is deletion compaction, not applicable */,
|
||||
files_overlap_ /* l0_files_might_overlap, not applicable */,
|
||||
CompactionReason::kExternalSstIngestion));
|
||||
CompactionReason::kExternalSstIngestion, "" /* trim_ts */,
|
||||
-1 /* score, not applicable */,
|
||||
files_overlap_ /* l0_files_might_overlap, not applicable */));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -780,7 +844,6 @@ void ExternalSstFileIngestionJob::Cleanup(const Status& status) {
|
||||
// We failed to add the files to the database
|
||||
// remove all the files we copied
|
||||
DeleteInternalFiles();
|
||||
consumed_seqno_count_ = 0;
|
||||
files_overlap_ = false;
|
||||
} else if (status.ok() && ingestion_options_.move_files) {
|
||||
// The files were moved and added successfully, remove original file links
|
||||
@@ -823,6 +886,10 @@ Status ExternalSstFileIngestionJob::ResetTableReader(
|
||||
Status status =
|
||||
fs_->NewRandomAccessFile(external_file, fo, &sst_file, nullptr);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to create random access file for external file %s: %s",
|
||||
external_file.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
Temperature updated_temp = sst_file->GetTemperature();
|
||||
@@ -945,6 +1012,10 @@ Status ExternalSstFileIngestionJob::SanityCheckTableProperties(
|
||||
// user_defined_timestamps_persisted flag for the file.
|
||||
file_to_ingest->user_defined_timestamps_persisted = false;
|
||||
} else if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"ValidateUserDefinedTimestampsOptions failed for external file %s: %s",
|
||||
external_file.c_str(), s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -969,6 +1040,9 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
Status status = fs_->GetFileSize(external_file, IOOptions(),
|
||||
&file_to_ingest->file_size, nullptr);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to get file size for external file %s: %s",
|
||||
external_file.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -985,15 +1059,52 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
/*user_defined_timestamps_persisted=*/true, sv,
|
||||
file_to_ingest, &table_reader);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to reset table reader for external file %s: %s",
|
||||
external_file.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
status = SanityCheckTableProperties(external_file, new_file_number, sv,
|
||||
file_to_ingest, &table_reader);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to sanity check table properties for external file %s: %s",
|
||||
external_file.c_str(), status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
const bool allow_data_in_errors = db_options_.allow_data_in_errors;
|
||||
ParsedInternalKey key;
|
||||
if (ingestion_options_.allow_db_generated_files) {
|
||||
// We are ingesting a DB generated SST file for which we don't reassign
|
||||
// sequence numbers. We need its smallest sequence number and largest
|
||||
// sequence number for FileMetaData.
|
||||
Status seqno_status = GetSeqnoBoundaryForFile(
|
||||
table_reader.get(), sv, file_to_ingest, allow_data_in_errors);
|
||||
|
||||
if (!seqno_status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to get sequence number boundary for external file %s: %s",
|
||||
external_file.c_str(), seqno_status.ToString().c_str());
|
||||
return seqno_status;
|
||||
}
|
||||
assert(file_to_ingest->smallest_seqno <= file_to_ingest->largest_seqno);
|
||||
assert(file_to_ingest->largest_seqno < kMaxSequenceNumber);
|
||||
} else {
|
||||
SequenceNumber largest_seqno =
|
||||
table_reader.get()->GetTableProperties()->key_largest_seqno;
|
||||
// UINT64_MAX means unknown and the file is generated before table property
|
||||
// `key_largest_seqno` is introduced.
|
||||
if (largest_seqno != UINT64_MAX && largest_seqno > 0) {
|
||||
return Status::Corruption(
|
||||
"External file has non zero largest sequence number " +
|
||||
std::to_string(largest_seqno));
|
||||
}
|
||||
}
|
||||
|
||||
if (ingestion_options_.verify_checksums_before_ingest) {
|
||||
// If customized readahead size is needed, we can pass a user option
|
||||
// all the way to here. Right now we just rely on the default readahead
|
||||
@@ -1005,11 +1116,13 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
status = table_reader->VerifyChecksum(
|
||||
ro, TableReaderCaller::kExternalSSTIngestion);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to verify checksum for table reader: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
ParsedInternalKey key;
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
@@ -1018,7 +1131,6 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
|
||||
|
||||
// Get first (smallest) and last (largest) key from file.
|
||||
bool allow_data_in_errors = db_options_.allow_data_in_errors;
|
||||
iter->SeekToFirst();
|
||||
if (iter->Valid()) {
|
||||
Status pik_status =
|
||||
@@ -1027,7 +1139,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
if (key.sequence != 0 && !ingestion_options_.allow_db_generated_files) {
|
||||
return Status::Corruption("External file has non zero sequence number");
|
||||
}
|
||||
file_to_ingest->smallest_internal_key.SetFrom(key);
|
||||
@@ -1064,41 +1176,13 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
if (key.sequence != 0 && !ingestion_options_.allow_db_generated_files) {
|
||||
return Status::Corruption("External file has non zero sequence number");
|
||||
}
|
||||
file_to_ingest->largest_internal_key.SetFrom(key);
|
||||
} else if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
SequenceNumber largest_seqno =
|
||||
table_reader.get()->GetTableProperties()->key_largest_seqno;
|
||||
// UINT64_MAX means unknown and the file is generated before table property
|
||||
// `key_largest_seqno` is introduced.
|
||||
if (largest_seqno != UINT64_MAX && largest_seqno > 0) {
|
||||
return Status::Corruption(
|
||||
"External file has non zero largest sequence number " +
|
||||
std::to_string(largest_seqno));
|
||||
}
|
||||
if (ingestion_options_.allow_db_generated_files &&
|
||||
largest_seqno == UINT64_MAX) {
|
||||
// Need to verify that all keys have seqno zero.
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
Status pik_status =
|
||||
ParseInternalKey(iter->key(), &key, allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::NotSupported(
|
||||
"External file has a key with non zero sequence number.");
|
||||
}
|
||||
}
|
||||
if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
@@ -1113,7 +1197,7 @@ Status ExternalSstFileIngestionJob::GetIngestedFileInfo(
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
if (key.sequence != 0 && !ingestion_options_.allow_db_generated_files) {
|
||||
return Status::Corruption(
|
||||
"External file has a range deletion with non zero sequence "
|
||||
"number.");
|
||||
@@ -1161,12 +1245,14 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
const size_t ts_sz = ucmp_->timestamp_size();
|
||||
assert(!prev_batch_uppermost_level.has_value() ||
|
||||
prev_batch_uppermost_level.value() < cfd_->NumberLevels());
|
||||
bool must_assign_to_l0 = prev_batch_uppermost_level.has_value() &&
|
||||
prev_batch_uppermost_level.value() == 0;
|
||||
if (force_global_seqno || files_overlap_ ||
|
||||
compaction_style == kCompactionStyleFIFO || must_assign_to_l0) {
|
||||
bool must_assign_to_l0 = (prev_batch_uppermost_level.has_value() &&
|
||||
prev_batch_uppermost_level.value() == 0) ||
|
||||
compaction_style == kCompactionStyleFIFO;
|
||||
|
||||
if (force_global_seqno || (!ingestion_options_.allow_db_generated_files &&
|
||||
(files_overlap_ || must_assign_to_l0))) {
|
||||
*assigned_seqno = last_seqno + 1;
|
||||
if (compaction_style == kCompactionStyleFIFO || must_assign_to_l0) {
|
||||
if (must_assign_to_l0) {
|
||||
assert(ts_sz == 0);
|
||||
file_to_ingest->picked_level = 0;
|
||||
if (ingestion_options_.fail_if_not_bottommost_level &&
|
||||
@@ -1187,16 +1273,26 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
ro.total_order_seek = true;
|
||||
int target_level = 0;
|
||||
auto* vstorage = cfd_->current()->storage_info();
|
||||
assert(!must_assign_to_l0);
|
||||
int exclusive_end_level = prev_batch_uppermost_level.has_value()
|
||||
? prev_batch_uppermost_level.value()
|
||||
: cfd_->NumberLevels();
|
||||
assert(!must_assign_to_l0 || ingestion_options_.allow_db_generated_files);
|
||||
int assigned_level_exclusive_end = cfd_->NumberLevels();
|
||||
if (must_assign_to_l0) {
|
||||
assigned_level_exclusive_end = 0;
|
||||
} else if (prev_batch_uppermost_level.has_value()) {
|
||||
assigned_level_exclusive_end = prev_batch_uppermost_level.value();
|
||||
}
|
||||
|
||||
for (int lvl = 0; lvl < exclusive_end_level; lvl++) {
|
||||
// When ingesting db generated files, we require that ingested files do not
|
||||
// overlap with any file in the DB. So we need to check all levels.
|
||||
int overlap_checking_exclusive_end =
|
||||
ingestion_options_.allow_db_generated_files
|
||||
? cfd_->NumberLevels()
|
||||
: assigned_level_exclusive_end;
|
||||
for (int lvl = 0; lvl < overlap_checking_exclusive_end; lvl++) {
|
||||
if (lvl > 0 && lvl < vstorage->base_level()) {
|
||||
continue;
|
||||
}
|
||||
if (atomic_replace_range_.has_value()) {
|
||||
if (lvl < assigned_level_exclusive_end &&
|
||||
atomic_replace_range_.has_value()) {
|
||||
target_level = lvl;
|
||||
continue;
|
||||
}
|
||||
@@ -1214,6 +1310,9 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
ro, env_options_, file_to_ingest->start_ukey,
|
||||
file_to_ingest->limit_ukey, lvl, &overlap_with_level);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to check overlap with level iterator: %s",
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
if (overlap_with_level) {
|
||||
@@ -1227,7 +1326,8 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
|
||||
// We don't overlap with any keys in this level, but we still need to check
|
||||
// if our file can fit in it
|
||||
if (IngestedFileFitInLevel(file_to_ingest, lvl)) {
|
||||
if (lvl < assigned_level_exclusive_end &&
|
||||
IngestedFileFitInLevel(file_to_ingest, lvl)) {
|
||||
target_level = lvl;
|
||||
}
|
||||
}
|
||||
@@ -1236,8 +1336,9 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
target_level < cfd_->NumberLevels() - 1) {
|
||||
status = Status::TryAgain(
|
||||
"Files cannot be ingested to Lmax. Please make sure key range of Lmax "
|
||||
"and ongoing compaction's output to Lmax"
|
||||
"does not overlap with files to ingest.");
|
||||
"and ongoing compaction's output to Lmax does not overlap with files "
|
||||
"to ingest. Input files overlapping with each other can cause some "
|
||||
"file to be assigned to non Lmax level.");
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1258,11 +1359,6 @@ Status ExternalSstFileIngestionJob::AssignLevelAndSeqnoForIngestedFile(
|
||||
}
|
||||
}
|
||||
|
||||
if (ingestion_options_.allow_db_generated_files && *assigned_seqno != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"An ingested file is assigned to a non-zero sequence number, which is "
|
||||
"incompatible with ingestion option allow_db_generated_files.");
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1279,13 +1375,13 @@ Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
|
||||
"at the last level!");
|
||||
}
|
||||
|
||||
// Second, check if despite allow_ingest_behind=true we still have 0 seqnums
|
||||
// at some upper level
|
||||
// Second, check if despite cf_allow_ingest_behind=true we still have 0
|
||||
// seqnums at some upper level
|
||||
for (int lvl = 0; lvl < cfd_->NumberLevels() - 1; lvl++) {
|
||||
for (auto file : vstorage->LevelFiles(lvl)) {
|
||||
if (file->fd.smallest_seqno == 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Can't ingest_behind file as despite allow_ingest_behind=true "
|
||||
"Can't ingest_behind file as despite cf_allow_ingest_behind=true "
|
||||
"there are files with 0 seqno in database at upper levels!");
|
||||
}
|
||||
}
|
||||
@@ -1297,8 +1393,12 @@ Status ExternalSstFileIngestionJob::CheckLevelForIngestedBehindFile(
|
||||
|
||||
Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
IngestedFileInfo* file_to_ingest, SequenceNumber seqno) {
|
||||
if (ingestion_options_.allow_db_generated_files) {
|
||||
assert(seqno == 0);
|
||||
assert(file_to_ingest->original_seqno == 0);
|
||||
}
|
||||
if (file_to_ingest->original_seqno == seqno) {
|
||||
// This file already have the correct global seqno
|
||||
// This file already has the correct global seqno.
|
||||
return Status::OK();
|
||||
} else if (!ingestion_options_.allow_global_seqno) {
|
||||
return Status::InvalidArgument("Global seqno is required, but disabled");
|
||||
@@ -1325,6 +1425,14 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
PutFixed64(&seqno_val, seqno);
|
||||
status = fsptr->Write(file_to_ingest->global_seqno_offset, seqno_val,
|
||||
IOOptions(), nullptr);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"Failed to write global seqno to %s: %s",
|
||||
file_to_ingest->internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
TEST_SYNC_POINT("ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno");
|
||||
status = SyncIngestedFile(fsptr.get());
|
||||
@@ -1341,6 +1449,11 @@ Status ExternalSstFileIngestionJob::AssignGlobalSeqnoForIngestedFile(
|
||||
return status;
|
||||
}
|
||||
} else if (!status.IsNotSupported()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to open ingested file %s for random read/write: %s",
|
||||
file_to_ingest->internal_file_path.c_str(),
|
||||
status.ToString().c_str());
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -1373,6 +1486,9 @@ IOStatus ExternalSstFileIngestionJob::GenerateChecksumForIngestedFile(
|
||||
db_options_.allow_mmap_reads, io_tracer_, db_options_.rate_limiter.get(),
|
||||
ro, db_options_.stats, db_options_.clock);
|
||||
if (!io_s.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "Failed to generate checksum for %s: %s",
|
||||
file_to_ingest->internal_file_path.c_str(), io_s.ToString().c_str());
|
||||
return io_s;
|
||||
}
|
||||
file_to_ingest->file_checksum = std::move(file_checksum);
|
||||
@@ -1412,4 +1528,91 @@ Status ExternalSstFileIngestionJob::SyncIngestedFile(TWritableFile* file) {
|
||||
}
|
||||
}
|
||||
|
||||
Status ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile(
|
||||
TableReader* table_reader, SuperVersion* sv,
|
||||
IngestedFileInfo* file_to_ingest, bool allow_data_in_errors) {
|
||||
const auto tp = table_reader->GetTableProperties();
|
||||
const bool has_largest_seqno = tp->HasKeyLargestSeqno();
|
||||
SequenceNumber largest_seqno = tp->key_largest_seqno;
|
||||
if (has_largest_seqno) {
|
||||
file_to_ingest->largest_seqno = largest_seqno;
|
||||
if (largest_seqno == 0) {
|
||||
file_to_ingest->smallest_seqno = 0;
|
||||
return Status::OK();
|
||||
}
|
||||
if (tp->HasKeySmallestSeqno()) {
|
||||
file_to_ingest->smallest_seqno = tp->key_smallest_seqno;
|
||||
return Status::OK();
|
||||
}
|
||||
}
|
||||
|
||||
// For older SST files they may not be recorded in table properties, so
|
||||
// we scan the file to find out.
|
||||
TEST_SYNC_POINT(
|
||||
"ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile:FileScan");
|
||||
SequenceNumber smallest_seqno = kMaxSequenceNumber;
|
||||
SequenceNumber largest_seqno_from_iter = 0;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = ingestion_options_.fill_cache;
|
||||
std::unique_ptr<InternalIterator> iter(table_reader->NewIterator(
|
||||
ro, sv->mutable_cf_options.prefix_extractor.get(), /*arena=*/nullptr,
|
||||
/*skip_filters=*/false, TableReaderCaller::kExternalSSTIngestion));
|
||||
ParsedInternalKey key;
|
||||
iter->SeekToFirst();
|
||||
while (iter->Valid()) {
|
||||
Status pik_status =
|
||||
ParseInternalKey(iter->key(), &key, allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
smallest_seqno = std::min(smallest_seqno, key.sequence);
|
||||
largest_seqno_from_iter = std::max(largest_seqno_from_iter, key.sequence);
|
||||
iter->Next();
|
||||
}
|
||||
if (!iter->status().ok()) {
|
||||
return iter->status();
|
||||
}
|
||||
|
||||
if (table_reader->GetTableProperties()->num_range_deletions > 0) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
table_reader->NewRangeTombstoneIterator(ro));
|
||||
if (range_del_iter != nullptr) {
|
||||
for (range_del_iter->SeekToFirst(); range_del_iter->Valid();
|
||||
range_del_iter->Next()) {
|
||||
Status pik_status =
|
||||
ParseInternalKey(range_del_iter->key(), &key, allow_data_in_errors);
|
||||
if (!pik_status.ok()) {
|
||||
return Status::Corruption("Corrupted key in external file. ",
|
||||
pik_status.getState());
|
||||
}
|
||||
smallest_seqno = std::min(smallest_seqno, key.sequence);
|
||||
largest_seqno_from_iter =
|
||||
std::max(largest_seqno_from_iter, key.sequence);
|
||||
}
|
||||
if (!range_del_iter->status().ok()) {
|
||||
return range_del_iter->status();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file_to_ingest->smallest_seqno = smallest_seqno;
|
||||
if (!has_largest_seqno) {
|
||||
file_to_ingest->largest_seqno = largest_seqno_from_iter;
|
||||
} else {
|
||||
assert(largest_seqno == largest_seqno_from_iter);
|
||||
file_to_ingest->largest_seqno = largest_seqno;
|
||||
}
|
||||
|
||||
if (file_to_ingest->largest_seqno == kMaxSequenceNumber) {
|
||||
return Status::InvalidArgument(
|
||||
"Unknown smallest seqno for db generated file.");
|
||||
}
|
||||
if (file_to_ingest->smallest_seqno == kMaxSequenceNumber) {
|
||||
return Status::InvalidArgument(
|
||||
"Unknown largest seqno for db generated file.");
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -180,6 +180,9 @@ struct IngestedFileInfo : public KeyRangeInfo {
|
||||
// the user key's format in the external file matches the column family's
|
||||
// setting.
|
||||
bool user_defined_timestamps_persisted = true;
|
||||
|
||||
SequenceNumber largest_seqno = kMaxSequenceNumber;
|
||||
SequenceNumber smallest_seqno = kMaxSequenceNumber;
|
||||
};
|
||||
|
||||
// A batch of files.
|
||||
@@ -230,7 +233,7 @@ class ExternalSstFileIngestionJob {
|
||||
directories_(directories),
|
||||
event_logger_(event_logger),
|
||||
job_start_time_(clock_->NowMicros()),
|
||||
consumed_seqno_count_(0),
|
||||
max_assigned_seqno_(0),
|
||||
io_tracer_(io_tracer) {
|
||||
assert(directories != nullptr);
|
||||
assert(cfd_);
|
||||
@@ -287,8 +290,16 @@ class ExternalSstFileIngestionJob {
|
||||
return files_to_ingest_;
|
||||
}
|
||||
|
||||
// How many sequence numbers did we consume as part of the ingestion job?
|
||||
int ConsumedSequenceNumbersCount() const { return consumed_seqno_count_; }
|
||||
// Return the maximum assigned sequence number for all files in this job.
|
||||
// When allow_db_generated_files = false, we may assign global sequence
|
||||
// numbers to ingested files. The global sequence numbers are sequence numbers
|
||||
// following versions_->LastSequence().
|
||||
// When allow_db_generated_files = true, we ingest files that already have
|
||||
// sequence numbers assigned. max_assigned_seqno_ will be the max sequence
|
||||
// number among ingested files.
|
||||
SequenceNumber MaxAssignedSequenceNumber() const {
|
||||
return max_assigned_seqno_;
|
||||
}
|
||||
|
||||
private:
|
||||
Status ResetTableReader(const std::string& external_file,
|
||||
@@ -349,7 +360,7 @@ class ExternalSstFileIngestionJob {
|
||||
std::optional<int> prev_batch_uppermost_level);
|
||||
|
||||
// File that we want to ingest behind always goes to the lowest level;
|
||||
// we just check that it fits in the level, that DB allows ingest_behind,
|
||||
// we just check that it fits in the level, that the CF allows ingest_behind,
|
||||
// and that we don't have 0 seqnums at the upper levels.
|
||||
// REQUIRES: Mutex held
|
||||
Status CheckLevelForIngestedBehindFile(IngestedFileInfo* file_to_ingest);
|
||||
@@ -369,6 +380,13 @@ class ExternalSstFileIngestionJob {
|
||||
template <typename TWritableFile>
|
||||
Status SyncIngestedFile(TWritableFile* file);
|
||||
|
||||
// Helper function to obtain the smallest and largest sequence number from a
|
||||
// file. When OK is returned, file_to_ingest->smallest_seqno and
|
||||
// file_to_ingest->largest_seqno will be updated.
|
||||
Status GetSeqnoBoundaryForFile(TableReader* table_reader, SuperVersion* sv,
|
||||
IngestedFileInfo* file_to_ingest,
|
||||
bool allow_data_in_errors);
|
||||
|
||||
// Create equivalent `Compaction` objects to this file ingestion job
|
||||
// , which will be used to check range conflict with other ongoing
|
||||
// compactions.
|
||||
@@ -395,7 +413,7 @@ class ExternalSstFileIngestionJob {
|
||||
EventLogger* event_logger_;
|
||||
VersionEdit edit_;
|
||||
uint64_t job_start_time_;
|
||||
int consumed_seqno_count_;
|
||||
SequenceNumber max_assigned_seqno_;
|
||||
// Set in ExternalSstFileIngestionJob::Prepare(), if true all files are
|
||||
// ingested in L0
|
||||
bool files_overlap_{false};
|
||||
|
||||
+597
-181
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -2417,102 +2418,130 @@ TEST_F(ExternalSSTFileTest, SnapshotInconsistencyBug) {
|
||||
}
|
||||
|
||||
TEST_P(ExternalSSTFileTest, IngestBehind) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 3;
|
||||
options.disable_auto_compactions = false;
|
||||
DestroyAndReopen(options);
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
std::map<std::string, std::string> true_data;
|
||||
for (bool cf_option : {false, true}) {
|
||||
SCOPED_TRACE("cf_option = " + std::to_string(cf_option));
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 3;
|
||||
options.disable_auto_compactions = false;
|
||||
DestroyAndReopen(options);
|
||||
std::vector<std::pair<std::string, std::string>> file_data;
|
||||
std::map<std::string, std::string> true_data;
|
||||
|
||||
// Insert 100 -> 200 into the memtable
|
||||
for (int i = 100; i <= 200; i++) {
|
||||
ASSERT_OK(Put(Key(i), "memtable"));
|
||||
}
|
||||
|
||||
// Insert 100 -> 200 using IngestExternalFile
|
||||
file_data.clear();
|
||||
for (int i = 0; i <= 20; i++) {
|
||||
file_data.emplace_back(Key(i), "ingest_behind");
|
||||
true_data[Key(i)] = "ingest_behind";
|
||||
}
|
||||
|
||||
bool allow_global_seqno = true;
|
||||
bool ingest_behind = true;
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
|
||||
// Can't ingest behind since allow_ingest_behind isn't set to true
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, ingest_behind, false /*sort_data*/,
|
||||
&true_data));
|
||||
|
||||
options.allow_ingest_behind = true;
|
||||
// check that we still can open the DB, as num_levels should be
|
||||
// sanitized to 3
|
||||
options.num_levels = 2;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
options.num_levels = 3;
|
||||
DestroyAndReopen(options);
|
||||
true_data.clear();
|
||||
// Insert 100 -> 200 into the memtable
|
||||
for (int i = 100; i <= 200; i++) {
|
||||
ASSERT_OK(Put(Key(i), "memtable"));
|
||||
true_data[Key(i)] = "memtable";
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// Universal picker should go at second from the bottom level
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, &true_data));
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
// this time ingest should fail as the file doesn't fit to the bottom level
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, &true_data));
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
|
||||
uint64_t ingested_file_number = level_to_files[2][0].fd.GetNumber();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// Last level should not be compacted
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
|
||||
ASSERT_EQ(ingested_file_number, level_to_files[2][0].fd.GetNumber());
|
||||
size_t kcnt = 0;
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
|
||||
// Auto-compaction should not include the last level.
|
||||
// Trigger compaction if size amplification exceeds 110%.
|
||||
options.compaction_options_universal.max_size_amplification_percent = 110;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
true_data[Key(j)] = rnd.RandomString(1000);
|
||||
ASSERT_OK(Put(Key(j), true_data[Key(j)]));
|
||||
// Insert 100 -> 200 into the memtable
|
||||
for (int i = 100; i <= 200; i++) {
|
||||
ASSERT_OK(Put(Key(i), "memtable"));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
|
||||
ASSERT_EQ(1, level_to_files[2].size());
|
||||
ASSERT_EQ(ingested_file_number, level_to_files[2][0].fd.GetNumber());
|
||||
|
||||
// Turning off the option allows DB to compact ingested files.
|
||||
options.allow_ingest_behind = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &level_to_files);
|
||||
ASSERT_EQ(1, level_to_files[2].size());
|
||||
ASSERT_NE(ingested_file_number, level_to_files[2][0].fd.GetNumber());
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
// Insert 100 -> 200 using IngestExternalFile
|
||||
file_data.clear();
|
||||
for (int i = 0; i <= 20; i++) {
|
||||
file_data.emplace_back(Key(i), "ingest_behind");
|
||||
true_data[Key(i)] = "ingest_behind";
|
||||
}
|
||||
|
||||
bool allow_global_seqno = true;
|
||||
bool ingest_behind = true;
|
||||
bool write_global_seqno = std::get<0>(GetParam());
|
||||
bool verify_checksums_before_ingest = std::get<1>(GetParam());
|
||||
|
||||
// Can't ingest behind since allow_ingest_behind isn't set to true
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, ingest_behind, false /*sort_data*/,
|
||||
&true_data));
|
||||
|
||||
if (cf_option) {
|
||||
options.cf_allow_ingest_behind = true;
|
||||
} else {
|
||||
options.allow_ingest_behind = true;
|
||||
}
|
||||
// check that we still can open the DB, as num_levels should be
|
||||
// sanitized to 3
|
||||
options.num_levels = 2;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
options.num_levels = 3;
|
||||
DestroyAndReopen(options);
|
||||
true_data.clear();
|
||||
// Insert 100 -> 200 into the memtable
|
||||
for (int i = 100; i <= 200; i++) {
|
||||
ASSERT_OK(Put(Key(i), "memtable"));
|
||||
true_data[Key(i)] = "memtable";
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// Universal picker should go at second from the bottom level
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
ASSERT_OK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, &true_data));
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
// this time ingest should fail as the file doesn't fit to the bottom level
|
||||
ASSERT_NOK(GenerateAndAddExternalFile(
|
||||
options, file_data, -1, allow_global_seqno, write_global_seqno,
|
||||
verify_checksums_before_ingest, true /*ingest_behind*/,
|
||||
false /*sort_data*/, &true_data));
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
std::vector<std::vector<FileMetaData>> level_to_files;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
uint64_t ingested_file_number = level_to_files[2][0].fd.GetNumber();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// Last level should not be compacted
|
||||
ASSERT_EQ("0,1,1", FilesPerLevel());
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
ASSERT_EQ(ingested_file_number, level_to_files[2][0].fd.GetNumber());
|
||||
size_t kcnt = 0;
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
|
||||
// Auto-compaction should not include the last level.
|
||||
// Trigger compaction if size amplification exceeds 110%.
|
||||
options.compaction_options_universal.max_size_amplification_percent = 110;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
true_data[Key(j)] = rnd.RandomString(1000);
|
||||
ASSERT_OK(Put(Key(j), true_data[Key(j)]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
ASSERT_EQ(1, level_to_files[2].size());
|
||||
ASSERT_EQ(ingested_file_number, level_to_files[2][0].fd.GetNumber());
|
||||
|
||||
// Turning off the option allows DB to compact ingested files.
|
||||
if (cf_option) {
|
||||
// Test that another CF does not allow ingest behind
|
||||
ColumnFamilyHandle* new_cfh;
|
||||
Options new_cf_option;
|
||||
ASSERT_OK(db_->CreateColumnFamily(new_cf_option, "new_cf", &new_cfh));
|
||||
ASSERT_TRUE(GenerateAndAddExternalFile(
|
||||
new_cf_option, file_data, -1, allow_global_seqno,
|
||||
write_global_seqno, verify_checksums_before_ingest,
|
||||
true /*ingest_behind*/, false /*sort_data*/, nullptr,
|
||||
/*cfh=*/new_cfh)
|
||||
.IsInvalidArgument());
|
||||
ASSERT_OK(db_->DropColumnFamily(new_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(new_cfh));
|
||||
|
||||
options.cf_allow_ingest_behind = false;
|
||||
} else {
|
||||
options.allow_ingest_behind = false;
|
||||
}
|
||||
ASSERT_OK(TryReopen(options));
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(),
|
||||
&level_to_files);
|
||||
ASSERT_EQ(1, level_to_files[2].size());
|
||||
ASSERT_NE(ingested_file_number, level_to_files[2][0].fd.GetNumber());
|
||||
VerifyDBFromMap(true_data, &kcnt, false);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileTest, SkipBloomFilter) {
|
||||
@@ -3514,19 +3543,26 @@ TEST_F(ExternalSSTFileWithTimestampTest, SanityCheck) {
|
||||
// overlapping key ranges.
|
||||
ASSERT_TRUE(IngestExternalUDTFile({file1, file2}).IsNotSupported());
|
||||
|
||||
options.allow_ingest_behind = true;
|
||||
DestroyAndReopen(options);
|
||||
IngestExternalFileOptions opts;
|
||||
for (bool cf_option : {false, true}) {
|
||||
SCOPED_TRACE("cf_option = " + std::to_string(cf_option));
|
||||
if (cf_option) {
|
||||
options.cf_allow_ingest_behind = true;
|
||||
} else {
|
||||
options.allow_ingest_behind = true;
|
||||
}
|
||||
DestroyAndReopen(options);
|
||||
IngestExternalFileOptions opts;
|
||||
|
||||
// TODO(yuzhangyu): support ingestion behind for user-defined timestamps?
|
||||
// Ingesting external files with user-defined timestamps requires searching
|
||||
// through the whole lsm tree to make sure there is no key range overlap with
|
||||
// the db. Ingestion behind currently is doing a simply placing it at the
|
||||
// bottom level step without a search, so we don't allow it either.
|
||||
opts.ingest_behind = true;
|
||||
ASSERT_TRUE(db_->IngestExternalFile({file1}, opts).IsNotSupported());
|
||||
// TODO(yuzhangyu): support ingestion behind for user-defined timestamps?
|
||||
// Ingesting external files with user-defined timestamps requires searching
|
||||
// through the whole lsm tree to make sure there is no key range overlap
|
||||
// with the db. Ingestion behind currently is doing a simply placing it at
|
||||
// the bottom level step without a search, so we don't allow it either.
|
||||
opts.ingest_behind = true;
|
||||
ASSERT_TRUE(db_->IngestExternalFile({file1}, opts).IsNotSupported());
|
||||
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
DestroyAndRecreateExternalSSTFilesDir();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(ExternalSSTFileWithTimestampTest, UDTSettingsCompatibilityCheck) {
|
||||
@@ -3818,99 +3854,32 @@ TEST_P(IngestDBGeneratedFileTest, FailureCase) {
|
||||
ASSERT_OK(Put(1, Key(k), "cf1_" + Key(k)));
|
||||
}
|
||||
ASSERT_OK(Flush(/*cf=*/1));
|
||||
{
|
||||
// Verify that largest key of the file has non-zero seqno.
|
||||
std::vector<std::vector<FileMetaData>> metadata;
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &metadata, nullptr);
|
||||
const FileMetaData& file = metadata[0][0];
|
||||
ValueType vtype;
|
||||
SequenceNumber seq;
|
||||
UnPackSequenceAndType(ExtractInternalKeyFooter(file.largest.Encode()),
|
||||
&seq, &vtype);
|
||||
ASSERT_GE(seq, 0);
|
||||
}
|
||||
std::vector<LiveFileMetaData> live_meta;
|
||||
db_->GetLiveFilesMetaData(&live_meta);
|
||||
ASSERT_EQ(live_meta.size(), 1);
|
||||
std::vector<std::string> to_ingest_files;
|
||||
to_ingest_files.emplace_back(live_meta[0].directory + "/" +
|
||||
live_meta[0].relative_filename);
|
||||
// Ingesting a file whose boundary key has non-zero seqno.
|
||||
Status s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
// This error msg is from checking seqno of boundary keys.
|
||||
ASSERT_TRUE(
|
||||
s.ToString().find("External file has non zero sequence number") !=
|
||||
std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
{
|
||||
// Only non-boundary key with non-zero seqno.
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ASSERT_OK(Put(1, Key(70), "cf1_" + Key(70)));
|
||||
ASSERT_OK(Flush(1));
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
|
||||
// Verify that only the non-boundary key of the file has non-zero seqno.
|
||||
std::vector<std::vector<FileMetaData>> metadata;
|
||||
// File may be at different level for different options.
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &metadata, nullptr);
|
||||
bool found_file = false;
|
||||
for (const auto& level : metadata) {
|
||||
if (level.empty()) {
|
||||
continue;
|
||||
}
|
||||
ASSERT_FALSE(found_file);
|
||||
found_file = true;
|
||||
ASSERT_EQ(1, level.size());
|
||||
const FileMetaData& file = level[0];
|
||||
ValueType vtype;
|
||||
SequenceNumber seq;
|
||||
UnPackSequenceAndType(ExtractInternalKeyFooter(file.largest.Encode()),
|
||||
&seq, &vtype);
|
||||
ASSERT_EQ(seq, 0);
|
||||
UnPackSequenceAndType(ExtractInternalKeyFooter(file.smallest.Encode()),
|
||||
&seq, &vtype);
|
||||
ASSERT_EQ(seq, 0);
|
||||
ASSERT_GT(file.fd.largest_seqno, 0);
|
||||
}
|
||||
ASSERT_TRUE(found_file);
|
||||
live_meta.clear();
|
||||
db_->GetLiveFilesMetaData(&live_meta);
|
||||
ASSERT_EQ(live_meta.size(), 1);
|
||||
to_ingest_files[0] =
|
||||
live_meta[0].directory + "/" + live_meta[0].relative_filename;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_NOK(s);
|
||||
// This error msg is from checking largest seqno in table property.
|
||||
ASSERT_TRUE(s.ToString().find("non zero largest sequence number") !=
|
||||
std::string::npos);
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
Status s;
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(db_->CompactRange(cro, handles_[1], nullptr, nullptr));
|
||||
live_meta.clear();
|
||||
|
||||
std::vector<LiveFileMetaData> live_meta;
|
||||
std::vector<std::string> to_ingest_files;
|
||||
db_->GetLiveFilesMetaData(&live_meta);
|
||||
ASSERT_EQ(live_meta.size(), 1);
|
||||
ASSERT_EQ(live_meta[0].column_family_name, "toto");
|
||||
ASSERT_EQ(0, live_meta[0].largest_seqno);
|
||||
to_ingest_files[0] =
|
||||
live_meta[0].directory + "/" + live_meta[0].relative_filename;
|
||||
to_ingest_files.emplace_back(live_meta[0].directory + "/" +
|
||||
live_meta[0].relative_filename);
|
||||
|
||||
// Ingesting a DB generated file with allow_db_generated_files = false
|
||||
ingest_opts.allow_db_generated_files = false;
|
||||
// Ingesting a DB genrate file with allow_db_generated_files = false;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_TRUE(s.ToString().find("External file version not found") !=
|
||||
std::string::npos);
|
||||
ASSERT_NOK(s);
|
||||
|
||||
const std::string err =
|
||||
"An ingested file is assigned to a non-zero sequence number, which is "
|
||||
"incompatible with ingestion option allow_db_generated_files";
|
||||
"An ingested file overlaps with existing data in the DB and has been "
|
||||
"assigned a non-zero sequence number";
|
||||
ingest_opts.allow_db_generated_files = true;
|
||||
s = db_->IngestExternalFile(to_ingest_files, ingest_opts);
|
||||
ASSERT_TRUE(s.ToString().find(err) != std::string::npos);
|
||||
@@ -4111,6 +4080,453 @@ TEST_P(IngestDBGeneratedFileTest2, NotOverlapWithDB) {
|
||||
}
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
TEST_P(IngestDBGeneratedFileTest2, NonZeroSeqno) {
|
||||
// Test ingestion of DB-generated SST files that contain non-zero sequence
|
||||
// numbers.
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
ingest_opts.allow_db_generated_files = true;
|
||||
// This only works since we are ingesting without snapshot
|
||||
// Failure case will be tested below.
|
||||
ingest_opts.snapshot_consistency = std::get<0>(GetParam());
|
||||
ingest_opts.allow_global_seqno = std::get<1>(GetParam());
|
||||
ingest_opts.allow_blocking_flush = std::get<2>(GetParam());
|
||||
ingest_opts.fail_if_not_bottommost_level = std::get<3>(GetParam());
|
||||
ingest_opts.link_files = std::get<4>(GetParam());
|
||||
Random* rnd = Random::GetTLSInstance();
|
||||
|
||||
do {
|
||||
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.allow_concurrent_memtable_write =
|
||||
false; // Required for VectorRepFactory
|
||||
CreateAndReopenWithCF({"non_overlap", "overlap"}, options);
|
||||
|
||||
ColumnFamilyHandle* non_overlap_cf = handles_[1];
|
||||
ColumnFamilyHandle* overlap_cf = handles_[2];
|
||||
|
||||
std::vector<std::string> expected_values;
|
||||
expected_values.resize(100);
|
||||
WriteOptions wo;
|
||||
// Setup target CF with non-overlapping base data Key1 and Key99
|
||||
// Will ingest keys [1, 98] below.
|
||||
expected_values[0] = rnd->RandomString(100);
|
||||
ASSERT_OK(db_->Put(wo, non_overlap_cf, Key(0), expected_values[0]));
|
||||
ASSERT_OK(db_->Flush({}, non_overlap_cf));
|
||||
expected_values[99] = rnd->RandomString(100);
|
||||
ASSERT_OK(db_->Put(wo, non_overlap_cf, Key(99), expected_values[99]));
|
||||
|
||||
// Set up overlapping cf
|
||||
ASSERT_OK(db_->Put(wo, overlap_cf, Key(50), rnd->RandomString(100)));
|
||||
|
||||
// Create temp CF/DB
|
||||
Options temp_cf_opts;
|
||||
ColumnFamilyHandle* temp_cfh = nullptr;
|
||||
DB* from_db = nullptr;
|
||||
std::string temp_db_name;
|
||||
// Using a separate DB also validates that latest sequence number
|
||||
// of target db is updated after ingestion (to the max sequence number
|
||||
// in ingested files).
|
||||
const bool use_temp_db = rnd->OneIn(2);
|
||||
SCOPED_TRACE("use_temp_db: " + std::to_string(use_temp_db));
|
||||
|
||||
std::vector<std::string> sst_file_paths;
|
||||
// optional L5: files in key range [70, 98]
|
||||
// L6: files in key range [1, 79]
|
||||
temp_cf_opts.target_file_size_base =
|
||||
20 << 10; // Small files to create multiple SSTs
|
||||
temp_cf_opts.num_levels = 7;
|
||||
temp_cf_opts.disable_auto_compactions = true; // Manually set up LSM
|
||||
temp_cf_opts.env = options.env;
|
||||
|
||||
if (use_temp_db) {
|
||||
temp_cf_opts.create_if_missing = true;
|
||||
temp_db_name = dbname_ + "/temp_db_" + std::to_string(rnd->Next());
|
||||
ASSERT_OK(DB::Open(temp_cf_opts, temp_db_name, &from_db));
|
||||
temp_cfh = from_db->DefaultColumnFamily();
|
||||
} else {
|
||||
from_db = db_;
|
||||
ASSERT_OK(
|
||||
from_db->CreateColumnFamily(temp_cf_opts, "temp_cf", &temp_cfh));
|
||||
}
|
||||
|
||||
// Use snapshot to ensure non-zero sequence numbers after compaction
|
||||
const Snapshot* snapshot = from_db->GetSnapshot();
|
||||
|
||||
for (int k = 1; k < 99; ++k) {
|
||||
expected_values[k] = rnd->RandomString(2000);
|
||||
ASSERT_OK(from_db->Put(wo, temp_cfh, Key(k), expected_values[k]));
|
||||
}
|
||||
ASSERT_OK(from_db->Flush({}, temp_cfh));
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForceOptimized;
|
||||
ASSERT_OK(from_db->CompactRange(cro, temp_cfh, nullptr, nullptr));
|
||||
|
||||
ASSERT_GT(NumTableFilesAtLevel(6, temp_cfh, from_db), 1);
|
||||
|
||||
const bool multi_level_ingestion = rnd->OneIn(2);
|
||||
SCOPED_TRACE("Multi-level ingestion: " +
|
||||
std::to_string(multi_level_ingestion));
|
||||
if (multi_level_ingestion) {
|
||||
for (int k = 80; k < 99; ++k) {
|
||||
expected_values[k] = rnd->RandomString(500);
|
||||
ASSERT_OK(from_db->Put(wo, temp_cfh, Key(k), expected_values[k]));
|
||||
}
|
||||
ASSERT_OK(from_db->Flush({}, temp_cfh));
|
||||
|
||||
// Do some overwrites, and overlap with previous L0 to avoid trivial move
|
||||
for (int k = 70; k < 82; ++k) {
|
||||
expected_values[k] = rnd->RandomString(500);
|
||||
ASSERT_OK(from_db->Put(wo, temp_cfh, Key(k), expected_values[k]));
|
||||
}
|
||||
ASSERT_OK(from_db->Flush({}, temp_cfh));
|
||||
|
||||
if (rnd->OneIn(2)) {
|
||||
MoveFilesToLevel(5, temp_cfh, from_db);
|
||||
ASSERT_GT(NumTableFilesAtLevel(5, temp_cfh, from_db), 0);
|
||||
}
|
||||
ASSERT_GT(NumTableFilesAtLevel(6, temp_cfh, from_db), 0);
|
||||
}
|
||||
SCOPED_TRACE("LSM of from_db " + FilesPerLevel(temp_cfh, from_db));
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
from_db->GetColumnFamilyMetaData(temp_cfh, &cf_meta);
|
||||
|
||||
// Iterate in reverse since IngestExternalFiles expect files to be ordered
|
||||
// from old to new
|
||||
for (auto level_meta = cf_meta.levels.rbegin();
|
||||
level_meta != cf_meta.levels.rend(); ++level_meta) {
|
||||
// L0 files need to be added in reverse order.
|
||||
for (auto file_meta = level_meta->files.rbegin();
|
||||
file_meta != level_meta->files.rend(); ++file_meta) {
|
||||
// Validate that files contain non-zero sequence numbers
|
||||
ASSERT_GT(file_meta->smallest_seqno, 0);
|
||||
ASSERT_GE(file_meta->largest_seqno, file_meta->smallest_seqno);
|
||||
sst_file_paths.emplace_back(file_meta->directory + "/" +
|
||||
file_meta->relative_filename);
|
||||
}
|
||||
}
|
||||
from_db->ReleaseSnapshot(snapshot);
|
||||
|
||||
Status s;
|
||||
// Perform ingestion and validate results
|
||||
if (multi_level_ingestion && options.num_levels > 1) {
|
||||
// fail_if_bottommost requres ingesting all files into the last level,
|
||||
// so it fails if we are assiging files to multiple levels.
|
||||
ingest_opts.fail_if_not_bottommost_level = true;
|
||||
s = db_->IngestExternalFile(non_overlap_cf, sst_file_paths, ingest_opts);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.ToString().find("Files cannot be ingested to Lmax") !=
|
||||
std::string::npos);
|
||||
ingest_opts.fail_if_not_bottommost_level = false;
|
||||
}
|
||||
if (ingest_opts.snapshot_consistency) {
|
||||
// snapshot_consisteny requires global sequence number assignment to
|
||||
// ingested files if there is any live snapshot.
|
||||
snapshot = db_->GetSnapshot();
|
||||
s = db_->IngestExternalFile(non_overlap_cf, sst_file_paths, ingest_opts);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.ToString().find(
|
||||
"An ingested file overlaps with existing data in the DB and has been "
|
||||
"assigned a non-zero sequence number"));
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
std::atomic<int> file_scan_count{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"ExternalSstFileIngestionJob::GetSeqnoBoundaryForFile:FileScan",
|
||||
[&](void* /*arg*/) { file_scan_count++; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile(non_overlap_cf, sst_file_paths, ingest_opts));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
EXPECT_EQ(file_scan_count, 0);
|
||||
|
||||
// Validate ingested data.
|
||||
ReadOptions ro;
|
||||
std::string val;
|
||||
for (int k = 0; k < 100; ++k) {
|
||||
s = db_->Get(ro, handles_[1], Key(k), &val);
|
||||
ASSERT_OK(s) << "Should find ingested key " << Key(k);
|
||||
ASSERT_EQ(val, expected_values[k]) << "key: " << Key(k);
|
||||
}
|
||||
|
||||
// Overlap with data in the CF
|
||||
if (ingest_opts.allow_blocking_flush) {
|
||||
s = db_->IngestExternalFile(overlap_cf, sst_file_paths, ingest_opts);
|
||||
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.ToString().find("An ingested file overlaps with existing "
|
||||
"data in the DB and has been "
|
||||
"assigned a non-zero sequence number") !=
|
||||
std::string::npos)
|
||||
<< s.ToString();
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
// FIXME: Without this, the test triggers some data race between dropping
|
||||
// CF and background compaction.
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
if (use_temp_db) {
|
||||
ASSERT_OK(from_db->Close());
|
||||
delete from_db;
|
||||
ASSERT_OK(DestroyDB(temp_db_name, temp_cf_opts));
|
||||
} else {
|
||||
ASSERT_OK(db_->DropColumnFamily(temp_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(temp_cfh));
|
||||
}
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
std::string GenSecondaryKey(const std::string& pk, const std::string& val) {
|
||||
return "index_" + val + "_" + pk;
|
||||
};
|
||||
|
||||
TEST_P(IngestDBGeneratedFileTest2, ZeroAndNonZeroSeqno) {
|
||||
// Test ingestion of SST files with zero and with non-zero sequence numbers.
|
||||
// Generate data using a temp CF and a temp DB:
|
||||
// 1. Temp CF with cf_allow_ingest_behind enabled to preserve non-zero seqno.
|
||||
// 2. Temp DB with everything compacted to have zero seqno.
|
||||
// Then ingest both types of files together into a target CF.
|
||||
// This mimics a user case where temp DB contains data read from a
|
||||
// snapshot while temp CF contains live writes after a snapshot is taken.
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
ingest_opts.allow_db_generated_files = true;
|
||||
ingest_opts.snapshot_consistency = std::get<0>(GetParam());
|
||||
ingest_opts.allow_global_seqno = std::get<1>(GetParam());
|
||||
ingest_opts.allow_blocking_flush = std::get<2>(GetParam());
|
||||
ingest_opts.fail_if_not_bottommost_level = std::get<3>(GetParam());
|
||||
ingest_opts.link_files = std::get<4>(GetParam());
|
||||
|
||||
Random* rnd = Random::GetTLSInstance();
|
||||
|
||||
do {
|
||||
SCOPED_TRACE("option_config_ = " + std::to_string(option_config_));
|
||||
Options options = CurrentOptions();
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
// Force more flushes/compactions and more files to be generated
|
||||
options.target_file_size_base = 1 << 10; // 1KB
|
||||
options.max_bytes_for_level_base = 2 << 10; // 2KB
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"target_cf"}, options);
|
||||
auto* target_cfh = handles_[1];
|
||||
|
||||
Options live_write_cf_opts = options;
|
||||
live_write_cf_opts.memtable_factory.reset(new VectorRepFactory());
|
||||
live_write_cf_opts.compaction_style = kCompactionStyleUniversal;
|
||||
live_write_cf_opts.cf_allow_ingest_behind = true;
|
||||
live_write_cf_opts.num_levels = 50;
|
||||
ColumnFamilyHandle* live_write_cfh;
|
||||
ASSERT_OK(db_->CreateColumnFamily(live_write_cf_opts, "live_write_cf",
|
||||
&live_write_cfh));
|
||||
|
||||
// Expected value and key
|
||||
std::map<std::string, std::string> expected;
|
||||
std::unordered_set<std::string> deleted;
|
||||
std::stringstream debug_info;
|
||||
|
||||
// Setup base data in target CF, will ingest keys with different prefixes
|
||||
// so they don't overlap with the base data.
|
||||
WriteOptions wo;
|
||||
for (int k = 0; k < 100; ++k) {
|
||||
int random_val = rnd->Uniform(20);
|
||||
expected[Key(k)] = std::to_string(random_val);
|
||||
ASSERT_OK(db_->Put(wo, target_cfh, Key(k), expected[Key(k)]));
|
||||
|
||||
// Force flush every 20 keys to create multiple SST files
|
||||
if (rnd->OneIn(20)) {
|
||||
ASSERT_OK(db_->Flush({}, target_cfh));
|
||||
debug_info << "Flush after " << k
|
||||
<< ", LSM state: " << FilesPerLevel(target_cfh) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Temp DB for snapshot data
|
||||
Options temp_db_opts;
|
||||
temp_db_opts.create_if_missing = true;
|
||||
temp_db_opts.target_file_size_base = 1 << 10;
|
||||
temp_db_opts.write_buffer_size = 1 << 10;
|
||||
temp_db_opts.memtable_factory.reset(new VectorRepFactory());
|
||||
temp_db_opts.allow_concurrent_memtable_write = false;
|
||||
temp_db_opts.compaction_style = kCompactionStyleUniversal;
|
||||
temp_db_opts.env = env_;
|
||||
temp_db_opts.num_levels = 7;
|
||||
|
||||
std::string temp_db_name =
|
||||
dbname_ + "/temp_db_" + std::to_string(rnd->Next());
|
||||
DB* temp_db = nullptr;
|
||||
ASSERT_OK(DB::Open(temp_db_opts, temp_db_name, &temp_db));
|
||||
|
||||
const Snapshot* snapshot = db_->GetSnapshot();
|
||||
ReadOptions ro;
|
||||
ro.snapshot = snapshot;
|
||||
ro.total_order_seek = true;
|
||||
std::unique_ptr<Iterator> iter{db_->NewIterator(ro, target_cfh)};
|
||||
// transform data read from snapshot and write to temp DB
|
||||
// Varying the number of files in temp DB.
|
||||
const int kValSize = rnd->Uniform(200);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
std::string key = iter->key().ToString();
|
||||
std::string value = iter->value().ToString();
|
||||
std::string sk = GenSecondaryKey(key, value);
|
||||
// Usually value is empty, here we use a larger value to generate
|
||||
// multiple SST files in temp_db.
|
||||
std::string sk_val = rnd->RandomString(kValSize);
|
||||
ASSERT_OK(temp_db->Put(wo, sk, sk_val));
|
||||
expected[sk] = sk_val;
|
||||
debug_info << "Snapshot data: " << sk << " -> \n";
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Do some live writes into target CF and live write CF.
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
WriteBatch wb;
|
||||
for (int j = 0; j < 5; ++j) {
|
||||
std::string key = Key(rnd->Uniform(100));
|
||||
std::string old_val = expected[key];
|
||||
// Value range is 0-19, allow some PK to have the same value.
|
||||
int random_val = rnd->Uniform(20);
|
||||
std::string new_val = std::to_string(random_val);
|
||||
std::string old_index_key = GenSecondaryKey(key, old_val);
|
||||
std::string new_index_key = GenSecondaryKey(key, new_val);
|
||||
ASSERT_OK(wb.SingleDelete(live_write_cfh, old_index_key));
|
||||
std::string sk_val = rnd->RandomString(kValSize);
|
||||
ASSERT_OK(wb.Put(live_write_cfh, new_index_key, sk_val));
|
||||
ASSERT_OK(wb.Put(target_cfh, key, new_val));
|
||||
expected[key] = new_val;
|
||||
expected.erase(old_index_key);
|
||||
expected[new_index_key] = sk_val;
|
||||
deleted.insert(old_index_key);
|
||||
deleted.erase(new_index_key);
|
||||
|
||||
debug_info << "Live write: SD " << old_index_key << "\n";
|
||||
debug_info << "Live write: " << key << " -> " << new_val << "\n";
|
||||
debug_info << "Live write: " << new_index_key << " -> \n";
|
||||
}
|
||||
ASSERT_OK(db_->Write(wo, &wb));
|
||||
if (rnd->OneIn(3)) {
|
||||
debug_info << "Flush after " << i << " live writes\n";
|
||||
ASSERT_OK(db_->Flush({}, live_write_cfh));
|
||||
}
|
||||
}
|
||||
iter.reset();
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
|
||||
// Compact temp_db to ensure zero sequence numbers
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(temp_db->CompactRange(cro, nullptr, nullptr));
|
||||
SCOPED_TRACE("Temp DB LSM: " +
|
||||
FilesPerLevel(temp_db->DefaultColumnFamily(), temp_db));
|
||||
|
||||
// Base data from snapshot
|
||||
std::vector<std::string> sst_file_paths_zero_seqno;
|
||||
|
||||
// Collect SST file paths with zero sequence numbers
|
||||
ASSERT_OK(temp_db->DisableFileDeletions());
|
||||
ColumnFamilyMetaData cf_meta_temp_db;
|
||||
temp_db->GetColumnFamilyMetaData(&cf_meta_temp_db);
|
||||
for (const auto& level_meta : cf_meta_temp_db.levels) {
|
||||
if (level_meta.level == 6) {
|
||||
for (const auto& file_meta : level_meta.files) {
|
||||
// Verify files have zero sequence numbers
|
||||
ASSERT_EQ(0, file_meta.largest_seqno)
|
||||
<< "File " << file_meta.relative_filename
|
||||
<< " should have zero sequence number\n"
|
||||
<< debug_info.str();
|
||||
sst_file_paths_zero_seqno.emplace_back(file_meta.directory + "/" +
|
||||
file_meta.relative_filename);
|
||||
}
|
||||
} else {
|
||||
// All files should be in L6
|
||||
ASSERT_EQ(0, level_meta.files.size()) << debug_info.str();
|
||||
}
|
||||
}
|
||||
|
||||
// Flush remaining catch up writes in memtable
|
||||
ASSERT_OK(db_->Flush({}, live_write_cfh));
|
||||
SCOPED_TRACE("LSM of live write cfh " + FilesPerLevel(live_write_cfh));
|
||||
// Collect SST file paths with non-zero sequence numbers
|
||||
ColumnFamilyMetaData live_write_cf_meta;
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
db_->GetColumnFamilyMetaData(live_write_cfh, &live_write_cf_meta);
|
||||
|
||||
// Live writes after snapshot
|
||||
std::vector<std::string> sst_file_paths_nonzero_seqno;
|
||||
for (auto level_meta = live_write_cf_meta.levels.rbegin();
|
||||
level_meta != live_write_cf_meta.levels.rend(); ++level_meta) {
|
||||
// Reverse order is important for L0, where recent updates are ordered
|
||||
// first
|
||||
for (auto file_meta = level_meta->files.rbegin();
|
||||
file_meta != level_meta->files.rend(); ++file_meta) {
|
||||
sst_file_paths_nonzero_seqno.emplace_back(file_meta->directory + "/" +
|
||||
file_meta->relative_filename);
|
||||
ASSERT_GT(file_meta->smallest_seqno, 0) << debug_info.str();
|
||||
}
|
||||
if (level_meta->level == 49) {
|
||||
// Ingest behind does not compact to the last level
|
||||
ASSERT_EQ(level_meta->files.size(), 0) << debug_info.str();
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_GT(sst_file_paths_zero_seqno.size(), 0) << debug_info.str();
|
||||
ASSERT_GT(sst_file_paths_nonzero_seqno.size(), 0) << debug_info.str();
|
||||
|
||||
// Combine all SST file paths.
|
||||
// File ingestion takes files from old to new.
|
||||
std::vector<std::string> all_sst_files;
|
||||
all_sst_files.insert(all_sst_files.end(), sst_file_paths_zero_seqno.begin(),
|
||||
sst_file_paths_zero_seqno.end());
|
||||
all_sst_files.insert(all_sst_files.end(),
|
||||
sst_file_paths_nonzero_seqno.begin(),
|
||||
sst_file_paths_nonzero_seqno.end());
|
||||
if (ingest_opts.fail_if_not_bottommost_level && options.num_levels > 1) {
|
||||
// overlapping files will be ingested into different levels, including non
|
||||
// Lmax
|
||||
Status s =
|
||||
db_->IngestExternalFile(target_cfh, all_sst_files, ingest_opts);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_TRUE(s.ToString().find("Files cannot be ingested to Lmax") !=
|
||||
std::string::npos);
|
||||
} else {
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile(target_cfh, all_sst_files, ingest_opts));
|
||||
|
||||
debug_info << "Zero seqno files: " << sst_file_paths_zero_seqno.size()
|
||||
<< "\nNon-zero seqno files: "
|
||||
<< sst_file_paths_nonzero_seqno.size() << "\n";
|
||||
|
||||
SCOPED_TRACE("Debug info:\n" + debug_info.str());
|
||||
VerifyDBFromMap(expected, nullptr, false, nullptr, target_cfh, &deleted);
|
||||
}
|
||||
|
||||
// clean up
|
||||
ASSERT_OK(db_->EnableFileDeletions());
|
||||
ASSERT_OK(temp_db->EnableFileDeletions());
|
||||
|
||||
// FIXME: Without this, the test triggers some data race between dropping
|
||||
// CF and background compaction.
|
||||
ASSERT_OK(db_->WaitForCompact({}));
|
||||
|
||||
ASSERT_OK(db_->DropColumnFamily(live_write_cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(live_write_cfh));
|
||||
|
||||
ASSERT_OK(temp_db->Close());
|
||||
delete temp_db;
|
||||
ASSERT_OK(DestroyDB(temp_db_name, temp_db_opts));
|
||||
} while (ChangeOptions(kSkipPlainTable | kSkipFIFOCompaction));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+3
-4
@@ -502,8 +502,7 @@ Status FlushJob::MemPurge() {
|
||||
kMaxSequenceNumber, &job_context_->snapshot_seqs, earliest_snapshot_,
|
||||
job_context_->earliest_write_conflict_snapshot,
|
||||
job_context_->GetJobSnapshotSequence(), job_context_->snapshot_checker,
|
||||
env, ShouldReportDetailedTime(env, ioptions.stats),
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
env, ShouldReportDetailedTime(env, ioptions.stats), range_del_agg.get(),
|
||||
nullptr, ioptions.allow_data_in_errors,
|
||||
ioptions.enforce_single_del_contracts,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
@@ -1105,13 +1104,13 @@ Status FlushJob::WriteLevel0Table() {
|
||||
const uint64_t micros = clock_->NowMicros() - start_micros;
|
||||
const uint64_t cpu_micros = clock_->CPUMicros() - start_cpu_micros;
|
||||
flush_stats.micros = micros;
|
||||
flush_stats.cpu_micros = cpu_micros;
|
||||
flush_stats.cpu_micros += cpu_micros;
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Flush lasted %" PRIu64
|
||||
" microseconds, and %" PRIu64 " cpu microseconds.\n",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, micros,
|
||||
cpu_micros);
|
||||
flush_stats.cpu_micros);
|
||||
|
||||
if (has_output) {
|
||||
flush_stats.bytes_written = meta_.fd.GetFileSize();
|
||||
|
||||
+16
-13
@@ -95,7 +95,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
uint64_t prospective_record_offset = 0;
|
||||
|
||||
Slice fragment;
|
||||
while (true) {
|
||||
for (;;) {
|
||||
uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size();
|
||||
size_t drop_size = 0;
|
||||
const uint8_t record_type =
|
||||
@@ -140,7 +140,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
prospective_record_offset = physical_record_offset;
|
||||
scratch->assign(fragment.data(), fragment.size());
|
||||
in_fragmented_record = true;
|
||||
break;
|
||||
break; // switch
|
||||
|
||||
case kMiddleType:
|
||||
case kRecyclableMiddleType:
|
||||
@@ -153,7 +153,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
}
|
||||
scratch->append(fragment.data(), fragment.size());
|
||||
}
|
||||
break;
|
||||
break; // switch
|
||||
|
||||
case kLastType:
|
||||
case kRecyclableLastType:
|
||||
@@ -171,7 +171,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
first_record_read_ = true;
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
break; // switch
|
||||
|
||||
case kSetCompressionType: {
|
||||
if (compression_type_record_read_) {
|
||||
@@ -193,7 +193,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
} else {
|
||||
InitCompression(compression_record);
|
||||
}
|
||||
break;
|
||||
break; // switch
|
||||
}
|
||||
case kPredecessorWALInfoType:
|
||||
case kRecyclePredecessorWALInfoType: {
|
||||
@@ -210,7 +210,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
MaybeVerifyPredecessorWALInfo(wal_recovery_mode, fragment,
|
||||
recorded_predecessor_wal_info);
|
||||
}
|
||||
break;
|
||||
break; // switch
|
||||
}
|
||||
case kUserDefinedTimestampSizeType:
|
||||
case kRecyclableUserDefinedTimestampSizeType: {
|
||||
@@ -235,7 +235,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
ReportCorruption(fragment.size(), s.getState());
|
||||
}
|
||||
}
|
||||
break;
|
||||
break; // switch
|
||||
}
|
||||
|
||||
case kBadHeader:
|
||||
@@ -304,7 +304,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
in_fragmented_record = false;
|
||||
scratch->clear();
|
||||
}
|
||||
break;
|
||||
break; // switch
|
||||
|
||||
case kBadRecordLen:
|
||||
if (eof_) {
|
||||
@@ -337,7 +337,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
in_fragmented_record = false;
|
||||
scratch->clear();
|
||||
}
|
||||
break;
|
||||
break; // switch
|
||||
|
||||
default: {
|
||||
if ((record_type & kRecordTypeSafeIgnoreMask) == 0) {
|
||||
@@ -349,11 +349,11 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
}
|
||||
in_fragmented_record = false;
|
||||
scratch->clear();
|
||||
break;
|
||||
break; // switch
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
// unreachable
|
||||
}
|
||||
|
||||
void Reader::MaybeVerifyPredecessorWALInfo(
|
||||
@@ -380,8 +380,11 @@ void Reader::MaybeVerifyPredecessorWALInfo(
|
||||
} else {
|
||||
if (observed_predecessor_wal_info_.GetLogNumber() !=
|
||||
recorded_predecessor_log_number) {
|
||||
std::string reason = "Missing WAL of log number " +
|
||||
std::to_string(recorded_predecessor_log_number);
|
||||
std::string reason =
|
||||
"Mismatched predecessor log number of WAL file " +
|
||||
file_->file_name() + " Recorded " +
|
||||
std::to_string(recorded_predecessor_log_number) + ". Observed " +
|
||||
std::to_string(observed_predecessor_wal_info_.GetLogNumber());
|
||||
ReportCorruption(fragment.size(), reason.c_str(),
|
||||
recorded_predecessor_log_number);
|
||||
} else if (observed_predecessor_wal_info_.GetLastSeqnoRecorded() !=
|
||||
|
||||
+84
-35
@@ -70,7 +70,9 @@ ImmutableMemTableOptions::ImmutableMemTableOptions(
|
||||
protection_bytes_per_key(
|
||||
mutable_cf_options.memtable_protection_bytes_per_key),
|
||||
allow_data_in_errors(ioptions.allow_data_in_errors),
|
||||
paranoid_memory_checks(mutable_cf_options.paranoid_memory_checks) {}
|
||||
paranoid_memory_checks(mutable_cf_options.paranoid_memory_checks),
|
||||
memtable_veirfy_per_key_checksum_on_seek(
|
||||
mutable_cf_options.memtable_veirfy_per_key_checksum_on_seek) {}
|
||||
|
||||
MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
const ImmutableOptions& ioptions,
|
||||
@@ -115,7 +117,13 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
oldest_key_time_(std::numeric_limits<uint64_t>::max()),
|
||||
approximate_memory_usage_(0),
|
||||
memtable_max_range_deletions_(
|
||||
mutable_cf_options.memtable_max_range_deletions) {
|
||||
mutable_cf_options.memtable_max_range_deletions),
|
||||
key_validation_callback_(
|
||||
(moptions_.protection_bytes_per_key != 0 &&
|
||||
moptions_.memtable_veirfy_per_key_checksum_on_seek)
|
||||
? std::bind(&MemTable::ValidateKey, this, std::placeholders::_1,
|
||||
std::placeholders::_2)
|
||||
: std::function<Status(const char*, bool)>(nullptr)) {
|
||||
UpdateFlushState();
|
||||
// something went wrong if we need to flush before inserting anything
|
||||
assert(!ShouldScheduleFlush());
|
||||
@@ -134,6 +142,16 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
auto new_cache = std::make_shared<FragmentedRangeTombstoneListCache>();
|
||||
size_t size = cached_range_tombstone_.Size();
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
#if defined(__cpp_lib_atomic_shared_ptr)
|
||||
std::atomic<std::shared_ptr<FragmentedRangeTombstoneListCache>>*
|
||||
local_cache_ref_ptr = cached_range_tombstone_.AccessAtCore(i);
|
||||
auto new_local_cache_ref = std::make_shared<
|
||||
const std::shared_ptr<FragmentedRangeTombstoneListCache>>(new_cache);
|
||||
std::shared_ptr<FragmentedRangeTombstoneListCache> aliased_ptr(
|
||||
new_local_cache_ref, new_cache.get());
|
||||
local_cache_ref_ptr->store(std::move(aliased_ptr),
|
||||
std::memory_order_relaxed);
|
||||
#else
|
||||
std::shared_ptr<FragmentedRangeTombstoneListCache>* local_cache_ref_ptr =
|
||||
cached_range_tombstone_.AccessAtCore(i);
|
||||
auto new_local_cache_ref = std::make_shared<
|
||||
@@ -143,6 +161,7 @@ MemTable::MemTable(const InternalKeyComparator& cmp,
|
||||
std::shared_ptr<FragmentedRangeTombstoneListCache>(new_local_cache_ref,
|
||||
new_cache.get()),
|
||||
std::memory_order_relaxed);
|
||||
#endif
|
||||
}
|
||||
const Comparator* ucmp = cmp.user_comparator();
|
||||
assert(ucmp);
|
||||
@@ -168,7 +187,7 @@ size_t MemTable::ApproximateMemoryUsage() {
|
||||
}
|
||||
total_usage += usage;
|
||||
}
|
||||
approximate_memory_usage_.store(total_usage, std::memory_order_relaxed);
|
||||
approximate_memory_usage_.StoreRelaxed(total_usage);
|
||||
// otherwise, return the actual usage
|
||||
return total_usage;
|
||||
}
|
||||
@@ -182,12 +201,12 @@ bool MemTable::ShouldFlushNow() {
|
||||
// This is set if memtable_max_range_deletions is > 0,
|
||||
// and that many range deletions are done
|
||||
if (memtable_max_range_deletions_ > 0 &&
|
||||
num_range_deletes_.load(std::memory_order_relaxed) >=
|
||||
num_range_deletes_.LoadRelaxed() >=
|
||||
static_cast<uint64_t>(memtable_max_range_deletions_)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t write_buffer_size = write_buffer_size_.load(std::memory_order_relaxed);
|
||||
size_t write_buffer_size = write_buffer_size_.LoadRelaxed();
|
||||
// In a lot of times, we cannot allocate arena blocks that exactly matches the
|
||||
// buffer size. Thus we have to decide if we should over-allocate or
|
||||
// under-allocate.
|
||||
@@ -200,10 +219,10 @@ bool MemTable::ShouldFlushNow() {
|
||||
assert(range_del_table_->ApproximateMemoryUsage() == 0);
|
||||
// If arena still have room for new block allocation, we can safely say it
|
||||
// shouldn't flush.
|
||||
auto allocated_memory = table_->ApproximateMemoryUsage() +
|
||||
arena_.MemoryAllocatedBytes();
|
||||
auto allocated_memory =
|
||||
table_->ApproximateMemoryUsage() + arena_.MemoryAllocatedBytes();
|
||||
|
||||
approximate_memory_usage_.store(allocated_memory, std::memory_order_relaxed);
|
||||
approximate_memory_usage_.StoreRelaxed(allocated_memory);
|
||||
|
||||
// if we can still allocate one more block without exceeding the
|
||||
// over-allocation ratio, then we should not flush.
|
||||
@@ -383,7 +402,11 @@ class MemTableIterator : public InternalIterator {
|
||||
!mem.GetImmutableMemTableOptions()->inplace_update_support),
|
||||
arena_mode_(arena != nullptr),
|
||||
paranoid_memory_checks_(mem.moptions_.paranoid_memory_checks),
|
||||
allow_data_in_error(mem.moptions_.allow_data_in_errors) {
|
||||
validate_on_seek_(
|
||||
mem.moptions_.paranoid_memory_checks ||
|
||||
mem.moptions_.memtable_veirfy_per_key_checksum_on_seek),
|
||||
allow_data_in_error_(mem.moptions_.allow_data_in_errors),
|
||||
key_validation_callback_(mem.key_validation_callback_) {
|
||||
if (kind == kRangeDelEntries) {
|
||||
iter_ = mem.range_del_table_->GetIterator(arena);
|
||||
} else if (prefix_extractor_ != nullptr &&
|
||||
@@ -452,8 +475,10 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error);
|
||||
if (validate_on_seek_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error_,
|
||||
paranoid_memory_checks_,
|
||||
key_validation_callback_);
|
||||
} else {
|
||||
iter_->Seek(k, nullptr);
|
||||
}
|
||||
@@ -477,8 +502,10 @@ class MemTableIterator : public InternalIterator {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error);
|
||||
if (validate_on_seek_) {
|
||||
status_ = iter_->SeekAndValidate(k, nullptr, allow_data_in_error_,
|
||||
paranoid_memory_checks_,
|
||||
key_validation_callback_);
|
||||
} else {
|
||||
iter_->Seek(k, nullptr);
|
||||
}
|
||||
@@ -507,7 +534,7 @@ class MemTableIterator : public InternalIterator {
|
||||
PERF_COUNTER_ADD(next_on_memtable_count, 1);
|
||||
assert(Valid());
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->NextAndValidate(allow_data_in_error);
|
||||
status_ = iter_->NextAndValidate(allow_data_in_error_);
|
||||
} else {
|
||||
iter_->Next();
|
||||
TEST_SYNC_POINT_CALLBACK("MemTableIterator::Next:0", iter_);
|
||||
@@ -529,7 +556,7 @@ class MemTableIterator : public InternalIterator {
|
||||
PERF_COUNTER_ADD(prev_on_memtable_count, 1);
|
||||
assert(Valid());
|
||||
if (paranoid_memory_checks_) {
|
||||
status_ = iter_->PrevAndValidate(allow_data_in_error);
|
||||
status_ = iter_->PrevAndValidate(allow_data_in_error_);
|
||||
} else {
|
||||
iter_->Prev();
|
||||
}
|
||||
@@ -588,7 +615,9 @@ class MemTableIterator : public InternalIterator {
|
||||
bool value_pinned_;
|
||||
bool arena_mode_;
|
||||
const bool paranoid_memory_checks_;
|
||||
const bool allow_data_in_error;
|
||||
const bool validate_on_seek_;
|
||||
const bool allow_data_in_error_;
|
||||
const std::function<Status(const char*, bool)> key_validation_callback_;
|
||||
|
||||
void VerifyEntryChecksum() {
|
||||
if (protection_bytes_per_key_ > 0 && Valid()) {
|
||||
@@ -745,7 +774,7 @@ FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq,
|
||||
bool immutable_memtable) {
|
||||
if (read_options.ignore_range_deletions ||
|
||||
is_range_del_table_empty_.load(std::memory_order_relaxed)) {
|
||||
is_range_del_table_empty_.LoadRelaxed()) {
|
||||
return nullptr;
|
||||
}
|
||||
return NewRangeTombstoneIteratorInternal(read_options, read_seq,
|
||||
@@ -756,7 +785,7 @@ FragmentedRangeTombstoneIterator*
|
||||
MemTable::NewTimestampStrippingRangeTombstoneIterator(
|
||||
const ReadOptions& read_options, SequenceNumber read_seq, size_t ts_sz) {
|
||||
if (read_options.ignore_range_deletions ||
|
||||
is_range_del_table_empty_.load(std::memory_order_relaxed)) {
|
||||
is_range_del_table_empty_.LoadRelaxed()) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!timestamp_stripping_fragmented_range_tombstone_list_) {
|
||||
@@ -790,8 +819,13 @@ FragmentedRangeTombstoneIterator* MemTable::NewRangeTombstoneIteratorInternal(
|
||||
|
||||
// takes current cache
|
||||
std::shared_ptr<FragmentedRangeTombstoneListCache> cache =
|
||||
#if defined(__cpp_lib_atomic_shared_ptr)
|
||||
cached_range_tombstone_.Access()->load(std::memory_order_relaxed)
|
||||
#else
|
||||
std::atomic_load_explicit(cached_range_tombstone_.Access(),
|
||||
std::memory_order_relaxed);
|
||||
std::memory_order_relaxed)
|
||||
#endif
|
||||
;
|
||||
// construct fragmented tombstone list if necessary
|
||||
if (!cache->initialized.load(std::memory_order_acquire)) {
|
||||
cache->reader_mutex.lock();
|
||||
@@ -815,7 +849,7 @@ void MemTable::ConstructFragmentedRangeTombstones() {
|
||||
// There should be no concurrent Construction.
|
||||
// We could also check fragmented_range_tombstone_list_ to avoid repeate
|
||||
// constructions. We just construct them here again to be safe.
|
||||
if (!is_range_del_table_empty_.load(std::memory_order_relaxed)) {
|
||||
if (!is_range_del_table_empty_.LoadRelaxed()) {
|
||||
// TODO: plumb Env::IOActivity, Env::IOPriority
|
||||
auto* unfragmented_iter = new MemTableIterator(
|
||||
MemTableIterator::kRangeDelEntries, *this, ReadOptions());
|
||||
@@ -838,7 +872,7 @@ ReadOnlyMemTable::MemTableStats MemTable::ApproximateStats(
|
||||
if (entry_count == 0) {
|
||||
return {0, 0};
|
||||
}
|
||||
uint64_t n = num_entries_.load(std::memory_order_relaxed);
|
||||
uint64_t n = num_entries_.LoadRelaxed();
|
||||
if (n == 0) {
|
||||
return {0, 0};
|
||||
}
|
||||
@@ -848,7 +882,7 @@ ReadOnlyMemTable::MemTableStats MemTable::ApproximateStats(
|
||||
// the inaccuracy.
|
||||
entry_count = n;
|
||||
}
|
||||
uint64_t data_size = data_size_.load(std::memory_order_relaxed);
|
||||
uint64_t data_size = data_size_.LoadRelaxed();
|
||||
return {entry_count * (data_size / n), entry_count};
|
||||
}
|
||||
|
||||
@@ -978,17 +1012,14 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
|
||||
// this is a bit ugly, but is the way to avoid locked instructions
|
||||
// when incrementing an atomic
|
||||
num_entries_.store(num_entries_.load(std::memory_order_relaxed) + 1,
|
||||
std::memory_order_relaxed);
|
||||
data_size_.store(data_size_.load(std::memory_order_relaxed) + encoded_len,
|
||||
std::memory_order_relaxed);
|
||||
num_entries_.StoreRelaxed(num_entries_.LoadRelaxed() + 1);
|
||||
data_size_.StoreRelaxed(data_size_.LoadRelaxed() + encoded_len);
|
||||
if (type == kTypeDeletion || type == kTypeSingleDeletion ||
|
||||
type == kTypeDeletionWithTimestamp) {
|
||||
num_deletes_.store(num_deletes_.load(std::memory_order_relaxed) + 1,
|
||||
std::memory_order_relaxed);
|
||||
num_deletes_.StoreRelaxed(num_deletes_.LoadRelaxed() + 1);
|
||||
} else if (type == kTypeRangeDeletion) {
|
||||
uint64_t val = num_range_deletes_.load(std::memory_order_relaxed) + 1;
|
||||
num_range_deletes_.store(val, std::memory_order_relaxed);
|
||||
uint64_t val = num_range_deletes_.LoadRelaxed() + 1;
|
||||
num_range_deletes_.StoreRelaxed(val);
|
||||
}
|
||||
|
||||
if (bloom_filter_ && prefix_extractor_ &&
|
||||
@@ -1059,6 +1090,16 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
range_del_mutex_.lock();
|
||||
}
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
#if defined(__cpp_lib_atomic_shared_ptr)
|
||||
std::atomic<std::shared_ptr<FragmentedRangeTombstoneListCache>>*
|
||||
local_cache_ref_ptr = cached_range_tombstone_.AccessAtCore(i);
|
||||
auto new_local_cache_ref = std::make_shared<
|
||||
const std::shared_ptr<FragmentedRangeTombstoneListCache>>(new_cache);
|
||||
std::shared_ptr<FragmentedRangeTombstoneListCache> aliased_ptr(
|
||||
new_local_cache_ref, new_cache.get());
|
||||
local_cache_ref_ptr->store(std::move(aliased_ptr),
|
||||
std::memory_order_relaxed);
|
||||
#else
|
||||
std::shared_ptr<FragmentedRangeTombstoneListCache>* local_cache_ref_ptr =
|
||||
cached_range_tombstone_.AccessAtCore(i);
|
||||
auto new_local_cache_ref = std::make_shared<
|
||||
@@ -1073,12 +1114,13 @@ Status MemTable::Add(SequenceNumber s, ValueType type,
|
||||
std::shared_ptr<FragmentedRangeTombstoneListCache>(
|
||||
new_local_cache_ref, new_cache.get()),
|
||||
std::memory_order_relaxed);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (allow_concurrent) {
|
||||
range_del_mutex_.unlock();
|
||||
}
|
||||
is_range_del_table_empty_.store(false, std::memory_order_relaxed);
|
||||
is_range_del_table_empty_.StoreRelaxed(false);
|
||||
}
|
||||
UpdateOldestKeyTime();
|
||||
|
||||
@@ -1469,11 +1511,13 @@ void MemTable::GetFromTable(const LookupKey& key,
|
||||
saver.allow_data_in_errors = moptions_.allow_data_in_errors;
|
||||
saver.protection_bytes_per_key = moptions_.protection_bytes_per_key;
|
||||
|
||||
if (!moptions_.paranoid_memory_checks) {
|
||||
if (!moptions_.paranoid_memory_checks &&
|
||||
!moptions_.memtable_veirfy_per_key_checksum_on_seek) {
|
||||
table_->Get(key, &saver, SaveValue);
|
||||
} else {
|
||||
Status check_s = table_->GetAndValidate(key, &saver, SaveValue,
|
||||
moptions_.allow_data_in_errors);
|
||||
Status check_s = table_->GetAndValidate(
|
||||
key, &saver, SaveValue, moptions_.allow_data_in_errors,
|
||||
moptions_.paranoid_memory_checks, key_validation_callback_);
|
||||
if (check_s.IsCorruption()) {
|
||||
*(saver.status) = check_s;
|
||||
// Should stop searching the LSM.
|
||||
@@ -1484,6 +1528,11 @@ void MemTable::GetFromTable(const LookupKey& key,
|
||||
*seq = saver.seq;
|
||||
}
|
||||
|
||||
Status MemTable::ValidateKey(const char* key, bool allow_data_in_errors) {
|
||||
return VerifyEntryChecksum(key, moptions_.protection_bytes_per_key,
|
||||
allow_data_in_errors);
|
||||
}
|
||||
|
||||
void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
ReadCallback* callback, bool immutable_memtable) {
|
||||
// The sequence number is updated synchronously in version_set.h
|
||||
@@ -1497,7 +1546,7 @@ void MemTable::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
// range tombstones. This is the simplest way to ensure range tombstones are
|
||||
// handled. TODO: allow Bloom checks where max_covering_tombstone_seq==0
|
||||
bool no_range_del = read_options.ignore_range_deletions ||
|
||||
is_range_del_table_empty_.load(std::memory_order_relaxed);
|
||||
is_range_del_table_empty_.LoadRelaxed();
|
||||
MultiGetRange temp_range(*range, range->begin(), range->end());
|
||||
if (bloom_filter_ && no_range_del) {
|
||||
bool whole_key =
|
||||
|
||||
+39
-34
@@ -8,7 +8,6 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -65,6 +64,7 @@ struct ImmutableMemTableOptions {
|
||||
uint32_t protection_bytes_per_key;
|
||||
bool allow_data_in_errors;
|
||||
bool paranoid_memory_checks;
|
||||
bool memtable_veirfy_per_key_checksum_on_seek;
|
||||
};
|
||||
|
||||
// Batched counters to updated when inserting keys in one write batch.
|
||||
@@ -568,7 +568,7 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
// As a cheap version of `ApproximateMemoryUsage()`, this function doesn't
|
||||
// require external synchronization. The value may be less accurate though
|
||||
size_t ApproximateMemoryUsageFast() const {
|
||||
return approximate_memory_usage_.load(std::memory_order_relaxed);
|
||||
return approximate_memory_usage_.LoadRelaxed();
|
||||
}
|
||||
|
||||
size_t MemoryAllocatedBytes() const override {
|
||||
@@ -689,16 +689,13 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
// Used in concurrent memtable inserts.
|
||||
void BatchPostProcess(const MemTablePostProcessInfo& update_counters) {
|
||||
table_->BatchPostProcess();
|
||||
num_entries_.fetch_add(update_counters.num_entries,
|
||||
std::memory_order_relaxed);
|
||||
data_size_.fetch_add(update_counters.data_size, std::memory_order_relaxed);
|
||||
num_entries_.FetchAddRelaxed(update_counters.num_entries);
|
||||
data_size_.FetchAddRelaxed(update_counters.data_size);
|
||||
if (update_counters.num_deletes != 0) {
|
||||
num_deletes_.fetch_add(update_counters.num_deletes,
|
||||
std::memory_order_relaxed);
|
||||
num_deletes_.FetchAddRelaxed(update_counters.num_deletes);
|
||||
}
|
||||
if (update_counters.num_range_deletes > 0) {
|
||||
num_range_deletes_.fetch_add(update_counters.num_range_deletes,
|
||||
std::memory_order_relaxed);
|
||||
num_range_deletes_.FetchAddRelaxed(update_counters.num_range_deletes);
|
||||
// noop for skip-list memtable
|
||||
// Besides correctness test in stress test, memtable flush record count
|
||||
// check will catch this if it were not noop.
|
||||
@@ -707,35 +704,26 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
UpdateFlushState();
|
||||
}
|
||||
|
||||
uint64_t NumEntries() const override {
|
||||
return num_entries_.load(std::memory_order_relaxed);
|
||||
}
|
||||
uint64_t NumEntries() const override { return num_entries_.LoadRelaxed(); }
|
||||
|
||||
uint64_t NumDeletion() const override {
|
||||
return num_deletes_.load(std::memory_order_relaxed);
|
||||
}
|
||||
uint64_t NumDeletion() const override { return num_deletes_.LoadRelaxed(); }
|
||||
|
||||
uint64_t NumRangeDeletion() const override {
|
||||
return num_range_deletes_.load(std::memory_order_relaxed);
|
||||
return num_range_deletes_.LoadRelaxed();
|
||||
}
|
||||
|
||||
uint64_t GetDataSize() const override {
|
||||
return data_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
uint64_t GetDataSize() const override { return data_size_.LoadRelaxed(); }
|
||||
|
||||
size_t write_buffer_size() const {
|
||||
return write_buffer_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
size_t write_buffer_size() const { return write_buffer_size_.LoadRelaxed(); }
|
||||
|
||||
// Dynamically change the memtable's capacity. If set below the current usage,
|
||||
// the next key added will trigger a flush. Can only increase size when
|
||||
// memtable prefix bloom is disabled, since we can't easily allocate more
|
||||
// space.
|
||||
// space. Non-atomic update ok because this is only called with DB mutex held.
|
||||
void UpdateWriteBufferSize(size_t new_write_buffer_size) {
|
||||
if (bloom_filter_ == nullptr ||
|
||||
new_write_buffer_size < write_buffer_size_) {
|
||||
write_buffer_size_.store(new_write_buffer_size,
|
||||
std::memory_order_relaxed);
|
||||
new_write_buffer_size < write_buffer_size_.LoadRelaxed()) {
|
||||
write_buffer_size_.StoreRelaxed(new_write_buffer_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,7 +815,7 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
|
||||
bool IsFragmentedRangeTombstonesConstructed() const override {
|
||||
return fragmented_range_tombstone_list_.get() != nullptr ||
|
||||
is_range_del_table_empty_;
|
||||
is_range_del_table_empty_.LoadRelaxed();
|
||||
}
|
||||
|
||||
// Gets the newest user defined timestamps in the memtable. This should only
|
||||
@@ -839,6 +827,9 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
uint32_t protection_bytes_per_key,
|
||||
bool allow_data_in_errors = false);
|
||||
|
||||
// Validate the checksum of the key/value pair.
|
||||
Status ValidateKey(const char* key, bool allow_data_in_errors);
|
||||
|
||||
private:
|
||||
enum FlushStateEnum { FLUSH_NOT_REQUESTED, FLUSH_REQUESTED, FLUSH_SCHEDULED };
|
||||
|
||||
@@ -853,16 +844,22 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
ConcurrentArena arena_;
|
||||
std::unique_ptr<MemTableRep> table_;
|
||||
std::unique_ptr<MemTableRep> range_del_table_;
|
||||
std::atomic_bool is_range_del_table_empty_;
|
||||
// This is OK to be relaxed access because consistency between table_ and
|
||||
// range_del_table_ is provided by explicit multi-versioning with sequence
|
||||
// numbers. It's ok for stale memory to say the range_del_table_ is empty when
|
||||
// it's actually not because if it was relevant to our read (based on sequence
|
||||
// number), the relaxed memory read would get a sufficiently updated value
|
||||
// because of the ordering provided by LastPublishedSequence().
|
||||
RelaxedAtomic<bool> is_range_del_table_empty_;
|
||||
|
||||
// Total data size of all data inserted
|
||||
std::atomic<uint64_t> data_size_;
|
||||
std::atomic<uint64_t> num_entries_;
|
||||
std::atomic<uint64_t> num_deletes_;
|
||||
std::atomic<uint64_t> num_range_deletes_;
|
||||
RelaxedAtomic<uint64_t> data_size_;
|
||||
RelaxedAtomic<uint64_t> num_entries_;
|
||||
RelaxedAtomic<uint64_t> num_deletes_;
|
||||
RelaxedAtomic<uint64_t> num_range_deletes_;
|
||||
|
||||
// Dynamically changeable memtable option
|
||||
std::atomic<size_t> write_buffer_size_;
|
||||
RelaxedAtomic<size_t> write_buffer_size_;
|
||||
|
||||
// The sequence number of the kv that was inserted first
|
||||
std::atomic<SequenceNumber> first_seqno_;
|
||||
@@ -898,7 +895,7 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
|
||||
// keep track of memory usage in table_, arena_, and range_del_table_.
|
||||
// Gets refreshed inside `ApproximateMemoryUsage()` or `ShouldFlushNow`
|
||||
std::atomic<uint64_t> approximate_memory_usage_;
|
||||
RelaxedAtomic<uint64_t> approximate_memory_usage_;
|
||||
|
||||
// max range deletions in a memtable, before automatic flushing, 0 for
|
||||
// unlimited.
|
||||
@@ -949,14 +946,22 @@ class MemTable final : public ReadOnlyMemTable {
|
||||
|
||||
// makes sure there is a single range tombstone writer to invalidate cache
|
||||
std::mutex range_del_mutex_;
|
||||
#if defined(__cpp_lib_atomic_shared_ptr)
|
||||
CoreLocalArray<
|
||||
std::atomic<std::shared_ptr<FragmentedRangeTombstoneListCache>>>
|
||||
cached_range_tombstone_;
|
||||
#else
|
||||
CoreLocalArray<std::shared_ptr<FragmentedRangeTombstoneListCache>>
|
||||
cached_range_tombstone_;
|
||||
|
||||
#endif
|
||||
void UpdateEntryChecksum(const ProtectionInfoKVOS64* kv_prot_info,
|
||||
const Slice& key, const Slice& value, ValueType type,
|
||||
SequenceNumber s, char* checksum_ptr);
|
||||
|
||||
void MaybeUpdateNewestUDT(const Slice& user_key);
|
||||
|
||||
const std::function<Status(const char*, bool)> key_validation_callback_;
|
||||
};
|
||||
|
||||
const char* EncodeKey(std::string* scratch, const Slice& target);
|
||||
|
||||
@@ -497,6 +497,7 @@ Status MergeHelper::MergeUntil(InternalIterator* iter,
|
||||
ikey.sequence <= latest_snapshot_
|
||||
? CompactionFilter::Decision::kKeep
|
||||
: FilterMerge(orig_ikey.user_key, value_slice);
|
||||
// FIXME: should also check for kRemove here
|
||||
if (filter != CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
range_del_agg != nullptr &&
|
||||
range_del_agg->ShouldDelete(
|
||||
|
||||
+1
-1
@@ -250,7 +250,7 @@ class MergeHelper {
|
||||
// Parallel with keys_; stores the operands
|
||||
mutable MergeContext merge_context_;
|
||||
|
||||
StopWatchNano filter_timer_;
|
||||
StopWatchNano<> filter_timer_;
|
||||
uint64_t total_filter_time_;
|
||||
Statistics* stats_;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ bool MergeOperator::FullMergeV3(const MergeOperationInputV3& merge_in,
|
||||
MergeOperationOutputV3* merge_out) const {
|
||||
assert(merge_out);
|
||||
|
||||
Slice value_of_default; // avoid warning about in_v2 pointing at this
|
||||
MergeOperationInput in_v2(merge_in.key, nullptr, merge_in.operand_list,
|
||||
merge_in.logger);
|
||||
|
||||
@@ -66,7 +67,6 @@ bool MergeOperator::FullMergeV3(const MergeOperationInputV3& merge_in,
|
||||
const bool has_default_column =
|
||||
WideColumnsHelper::HasDefaultColumn(existing_columns);
|
||||
|
||||
Slice value_of_default;
|
||||
if (has_default_column) {
|
||||
value_of_default = existing_columns.front().value();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
using MultiScanIterator = MultiScan::MultiScanIterator;
|
||||
|
||||
MultiScan::MultiScan(const ReadOptions& read_options,
|
||||
const MultiScanArgs& scan_opts, DB* db,
|
||||
ColumnFamilyHandle* cfh)
|
||||
: read_options_(read_options), scan_opts_(scan_opts), db_(db), cfh_(cfh) {
|
||||
bool slow_path = false;
|
||||
// Setup read_options with iterate_uuper_bound based on the first scan.
|
||||
// Subsequent scans will update and allocate a new DB iterator as necessary
|
||||
if (scan_opts.GetScanRanges()[0].range.limit) {
|
||||
upper_bound_ = *scan_opts.GetScanRanges()[0].range.limit;
|
||||
read_options_.iterate_upper_bound = &upper_bound_;
|
||||
} else {
|
||||
read_options_.iterate_upper_bound = nullptr;
|
||||
}
|
||||
for (const auto& opts : scan_opts.GetScanRanges()) {
|
||||
// Check that all the ScanOptions either specify an upper bound or not. If
|
||||
// its mixed we take the slow path which avoids calling Prepare: we have to
|
||||
// reallocate the Iterator with updated read_options everytime we switch
|
||||
// between upper bound or no upper bound, which complicates Prepare.
|
||||
if (opts.range.limit.has_value() !=
|
||||
scan_opts.GetScanRanges()[0].range.limit.has_value()) {
|
||||
slow_path = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
db_iter_.reset(db->NewIterator(read_options_, cfh));
|
||||
if (!slow_path) {
|
||||
db_iter_->Prepare(scan_opts);
|
||||
}
|
||||
}
|
||||
|
||||
MultiScanIterator& MultiScanIterator::operator++() {
|
||||
status_ = db_iter_->status();
|
||||
if (!status_.ok()) {
|
||||
throw MultiScanException(status_);
|
||||
}
|
||||
|
||||
if (idx_ >= scan_opts_.size()) {
|
||||
throw std::logic_error("Index out of range");
|
||||
}
|
||||
idx_++;
|
||||
if (idx_ < scan_opts_.size()) {
|
||||
// Check if we need to update read_options_
|
||||
if (scan_opts_[idx_].range.limit.has_value() !=
|
||||
(read_options_.iterate_upper_bound != nullptr)) {
|
||||
if (scan_opts_[idx_].range.limit) {
|
||||
*upper_bound_ = *scan_opts_[idx_].range.limit;
|
||||
read_options_.iterate_upper_bound = upper_bound_;
|
||||
} else {
|
||||
read_options_.iterate_upper_bound = nullptr;
|
||||
}
|
||||
db_iter_.reset(db_->NewIterator(read_options_, cfh_));
|
||||
scan_.Reset(db_iter_.get());
|
||||
} else if (scan_opts_[idx_].range.limit) {
|
||||
*upper_bound_ = *scan_opts_[idx_].range.limit;
|
||||
}
|
||||
db_iter_->Seek(*scan_opts_[idx_].range.start);
|
||||
status_ = db_iter_->status();
|
||||
if (!status_.ok()) {
|
||||
throw MultiScanException(status_);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+1
-1
@@ -396,7 +396,7 @@ const char* VersionEdit::DecodeNewFile4From(Slice* input) {
|
||||
return "temperature field wrong size";
|
||||
} else {
|
||||
Temperature casted_field = static_cast<Temperature>(field[0]);
|
||||
if (casted_field <= Temperature::kCold) {
|
||||
if (casted_field < Temperature::kLastTemperature) {
|
||||
f.temperature = casted_field;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ constexpr uint64_t kUnknownOldestAncesterTime = 0;
|
||||
constexpr uint64_t kUnknownNewestKeyTime = 0;
|
||||
constexpr uint64_t kUnknownFileCreationTime = 0;
|
||||
constexpr uint64_t kUnknownEpochNumber = 0;
|
||||
// If `Options::allow_ingest_behind` is true, this epoch number
|
||||
// If `Options::cf_allow_ingest_behind` is true, this epoch number
|
||||
// will be dedicated to files ingested behind.
|
||||
constexpr uint64_t kReservedEpochNumberForFileIngestedBehind = 1;
|
||||
|
||||
|
||||
+37
-17
@@ -117,21 +117,43 @@ Status ListColumnFamiliesHandler::ApplyVersionEdit(
|
||||
return s;
|
||||
}
|
||||
|
||||
Status FileChecksumRetriever::ApplyVersionEdit(VersionEdit& edit,
|
||||
ColumnFamilyData** /*unused*/) {
|
||||
for (const auto& deleted_file : edit.GetDeletedFiles()) {
|
||||
Status s = file_checksum_list_.RemoveOneFileChecksum(deleted_file.second);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
Status FileChecksumRetriever::FetchFileChecksumList(
|
||||
FileChecksumList& file_checksum_list) {
|
||||
Status s = Status::OK();
|
||||
for (const auto& [cf, file_checksums] : cf_file_checksums_) {
|
||||
[[maybe_unused]] const auto& _ = cf;
|
||||
for (const auto& [file_number, info] : file_checksums) {
|
||||
if (!(s = file_checksum_list.InsertOneFileChecksum(
|
||||
file_number, info.first, info.second))
|
||||
.ok()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& new_file : edit.GetNewFiles()) {
|
||||
Status s = file_checksum_list_.InsertOneFileChecksum(
|
||||
new_file.second.fd.GetNumber(), new_file.second.file_checksum,
|
||||
new_file.second.file_checksum_func_name);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
return s;
|
||||
}
|
||||
|
||||
Status FileChecksumRetriever::ApplyVersionEdit(VersionEdit& edit,
|
||||
ColumnFamilyData** /*unused*/) {
|
||||
uint32_t column_family_id = edit.GetColumnFamily();
|
||||
if (edit.IsColumnFamilyDrop()) {
|
||||
cf_file_checksums_.erase(column_family_id);
|
||||
}
|
||||
for (const auto& deleted_file : edit.GetDeletedFiles()) {
|
||||
if (cf_file_checksums_.find(column_family_id) == cf_file_checksums_.end()) {
|
||||
return Status::NotFound();
|
||||
}
|
||||
if (cf_file_checksums_[column_family_id].find(deleted_file.second) ==
|
||||
cf_file_checksums_[column_family_id].end()) {
|
||||
return Status::NotFound();
|
||||
}
|
||||
cf_file_checksums_[column_family_id].erase(deleted_file.second);
|
||||
}
|
||||
for (const auto& new_file : edit.GetNewFiles()) {
|
||||
cf_file_checksums_[column_family_id].emplace(
|
||||
new_file.second.fd.GetNumber(),
|
||||
std::make_pair(new_file.second.file_checksum,
|
||||
new_file.second.file_checksum_func_name));
|
||||
}
|
||||
for (const auto& new_blob_file : edit.GetBlobFileAdditions()) {
|
||||
std::string checksum_value = new_blob_file.GetChecksumValue();
|
||||
@@ -141,11 +163,9 @@ Status FileChecksumRetriever::ApplyVersionEdit(VersionEdit& edit,
|
||||
checksum_value = kUnknownFileChecksum;
|
||||
checksum_method = kUnknownFileChecksumFuncName;
|
||||
}
|
||||
Status s = file_checksum_list_.InsertOneFileChecksum(
|
||||
new_blob_file.GetBlobFileNumber(), checksum_value, checksum_method);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
cf_file_checksums_[column_family_id].emplace(
|
||||
new_blob_file.GetBlobFileNumber(),
|
||||
std::make_pair(checksum_value, checksum_method));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -80,19 +80,42 @@ class ListColumnFamiliesHandler : public VersionEditHandlerBase {
|
||||
|
||||
class FileChecksumRetriever : public VersionEditHandlerBase {
|
||||
public:
|
||||
FileChecksumRetriever(const ReadOptions& read_options, uint64_t max_read_size,
|
||||
FileChecksumList& file_checksum_list)
|
||||
: VersionEditHandlerBase(read_options, max_read_size),
|
||||
file_checksum_list_(file_checksum_list) {}
|
||||
FileChecksumRetriever(const ReadOptions& read_options, uint64_t max_read_size)
|
||||
: VersionEditHandlerBase(read_options, max_read_size) {}
|
||||
|
||||
~FileChecksumRetriever() override {}
|
||||
|
||||
Status FetchFileChecksumList(FileChecksumList& file_checksum_list);
|
||||
|
||||
protected:
|
||||
Status ApplyVersionEdit(VersionEdit& edit,
|
||||
ColumnFamilyData** /*unused*/) override;
|
||||
|
||||
private:
|
||||
FileChecksumList& file_checksum_list_;
|
||||
// Map from CF to file # to string pair, where first portion of the value
|
||||
// is checksum, and second portion of the value is checksum function name.
|
||||
//
|
||||
// [column family id A]
|
||||
// |
|
||||
// |-- [file #1] -> [checksum #1, checksum function name #1]
|
||||
// |-- [file #2] -> [checksum #2, checksum function name #2]
|
||||
// |
|
||||
// ...
|
||||
// |
|
||||
// |-- [file #N] -> [checksum #N, checksum function name #N]
|
||||
// [column family id B]
|
||||
// |
|
||||
// |-- [file #1] -> [checksum #1, checksum function name #1]
|
||||
// |
|
||||
// ...
|
||||
// |
|
||||
// |-- [file #M] -> [checksum #M, checksum function name #M]
|
||||
// |
|
||||
// ...
|
||||
std::unordered_map<
|
||||
uint32_t,
|
||||
std::unordered_map<uint64_t, std::pair<std::string, std::string>>>
|
||||
cf_file_checksums_;
|
||||
};
|
||||
|
||||
using VersionBuilderUPtr = std::unique_ptr<BaseReferencedVersionBuilder>;
|
||||
|
||||
+174
-22
@@ -95,6 +95,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
using ScanOptionsMap = std::unordered_map<size_t, MultiScanArgs>;
|
||||
|
||||
// Find File in LevelFilesBrief data structure
|
||||
// Within an index range defined by left and right
|
||||
int FindFileInRange(const InternalKeyComparator& icmp,
|
||||
@@ -1099,10 +1101,105 @@ class LevelIterator final : public InternalIterator {
|
||||
read_seq_ = read_seq;
|
||||
}
|
||||
|
||||
void Prepare(const std::vector<ScanOptions>* scan_opts) override {
|
||||
scan_opts_ = scan_opts;
|
||||
if (file_iter_.iter()) {
|
||||
file_iter_.Prepare(scan_opts_);
|
||||
inline bool FileHasMultiScanArg(size_t file_index) {
|
||||
if (file_to_scan_opts_.get()) {
|
||||
auto it = file_to_scan_opts_->find(file_index);
|
||||
if (it != file_to_scan_opts_->end()) {
|
||||
return !it->second.empty();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MultiScanArgs& GetMultiScanArgForFile(size_t file_index) {
|
||||
auto multi_scan_args_it = file_to_scan_opts_->find(file_index);
|
||||
if (multi_scan_args_it == file_to_scan_opts_->end()) {
|
||||
auto ret = file_to_scan_opts_->emplace(
|
||||
file_index, MultiScanArgs(user_comparator_.user_comparator()));
|
||||
multi_scan_args_it = ret.first;
|
||||
assert(ret.second);
|
||||
}
|
||||
return multi_scan_args_it->second;
|
||||
}
|
||||
|
||||
void Prepare(const MultiScanArgs* so) override {
|
||||
// We assume here that scan_opts is sorted such that
|
||||
// scan_opts[0].range.start < scan_opts[1].range.start, and non overlapping
|
||||
if (so == nullptr) {
|
||||
return;
|
||||
}
|
||||
scan_opts_ = so;
|
||||
|
||||
// Verify comparator is consistent
|
||||
assert(so->GetComparator() == user_comparator_.user_comparator());
|
||||
|
||||
file_to_scan_opts_ = std::make_unique<ScanOptionsMap>();
|
||||
for (size_t k = 0; k < scan_opts_->size(); k++) {
|
||||
const ScanOptions& opt = scan_opts_->GetScanRanges().at(k);
|
||||
auto start = opt.range.start;
|
||||
auto end = opt.range.limit;
|
||||
|
||||
if (!start.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We can capture this case in the future, but for now lets skip this.
|
||||
if (!end.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t timestamp_size =
|
||||
user_comparator_.user_comparator()->timestamp_size();
|
||||
InternalKey istart, iend;
|
||||
if (timestamp_size == 0) {
|
||||
istart =
|
||||
InternalKey(start.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
// end key is exclusive for multiscan
|
||||
iend = InternalKey(end.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
} else {
|
||||
std::string start_key_with_ts, end_key_with_ts;
|
||||
AppendKeyWithMaxTimestamp(&start_key_with_ts, start.value(),
|
||||
timestamp_size);
|
||||
AppendKeyWithMaxTimestamp(&end_key_with_ts, end.value(),
|
||||
timestamp_size);
|
||||
istart = InternalKey(start_key_with_ts, kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
// end key is exclusive for multiscan
|
||||
iend =
|
||||
InternalKey(end_key_with_ts, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
}
|
||||
|
||||
// TODO: This needs to be optimized, right now we iterate twice, which
|
||||
// we dont need to. We can do this in N rather than 2N.
|
||||
size_t fstart = FindFile(icomparator_, *flevel_, istart.Encode());
|
||||
size_t fend = FindFile(icomparator_, *flevel_, iend.Encode());
|
||||
|
||||
// We need to check the relevant cases
|
||||
// Cases:
|
||||
// 1. [ S E ]
|
||||
// 2. [ S ] [ E ]
|
||||
// 3. [ S ] ...... [ E ]
|
||||
for (auto i = fstart; i <= fend; i++) {
|
||||
if (i < flevel_->num_files) {
|
||||
// FindFile only compares against the largest_key, so we need this
|
||||
// additional check to ensure the scan range overlaps the file
|
||||
if (icomparator_.InternalKeyComparator::Compare(
|
||||
iend.Encode(), flevel_->files[i].smallest_key) < 0) {
|
||||
continue;
|
||||
}
|
||||
auto const metadata = flevel_->files[i].file_metadata;
|
||||
if (metadata->FileIsStandAloneRangeTombstone()) {
|
||||
// Skip stand alone range deletion files.
|
||||
continue;
|
||||
}
|
||||
auto& args = GetMultiScanArgForFile(i);
|
||||
args.insert(start.value(), end.value(), opt.property_bag);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Propagate multiscan configs
|
||||
for (auto& file_to_arg : *file_to_scan_opts_) {
|
||||
file_to_arg.second.CopyConfigFrom(*so);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1178,6 +1275,10 @@ class LevelIterator final : public InternalIterator {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool OverlapRange(const ScanOptions& opts);
|
||||
#endif
|
||||
|
||||
TableCache* table_cache_;
|
||||
const ReadOptions& read_options_;
|
||||
const FileOptions& file_options_;
|
||||
@@ -1231,7 +1332,10 @@ class LevelIterator final : public InternalIterator {
|
||||
bool prefix_exhausted_ = false;
|
||||
// Whether next/prev key is a sentinel key.
|
||||
bool to_return_sentinel_ = false;
|
||||
const std::vector<ScanOptions>* scan_opts_;
|
||||
const MultiScanArgs* scan_opts_ = nullptr;
|
||||
|
||||
// Our stored scan_opts for each prefix
|
||||
std::unique_ptr<ScanOptionsMap> file_to_scan_opts_ = nullptr;
|
||||
|
||||
// Sets flags for if we should return the sentinel key next.
|
||||
// The condition for returning sentinel is reaching the end of current
|
||||
@@ -1272,6 +1376,14 @@ void LevelIterator::Seek(const Slice& target) {
|
||||
}
|
||||
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
if (scan_opts_) {
|
||||
// At this point, we only know that the seek target is < largest_key
|
||||
// in the file. We need to check whether there is actual overlap.
|
||||
const FdWithKeyRange& cur_file = flevel_->files[file_index_];
|
||||
if (KeyReachedUpperBound(cur_file.smallest_key)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
file_iter_.Seek(target);
|
||||
// Status::TryAgain indicates asynchronous request for retrieval of data
|
||||
// blocks has been submitted. So it should return at this point and Seek
|
||||
@@ -1494,7 +1606,20 @@ bool LevelIterator::SkipEmptyFileForward() {
|
||||
// LevelIterator::Seek*, it should also call Seek* into the corresponding
|
||||
// range tombstone iterator.
|
||||
if (file_iter_.iter() != nullptr) {
|
||||
file_iter_.SeekToFirst();
|
||||
// If we are doing prepared scan opts then we should seek to the values
|
||||
// specified by the scan opts
|
||||
|
||||
if (scan_opts_ && FileHasMultiScanArg(file_index_)) {
|
||||
const ScanOptions& opts =
|
||||
GetMultiScanArgForFile(file_index_).GetScanRanges().front();
|
||||
if (opts.range.start.has_value()) {
|
||||
InternalKey target(*opts.range.start.AsPtr(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
file_iter_.Seek(target.Encode());
|
||||
}
|
||||
} else {
|
||||
file_iter_.SeekToFirst();
|
||||
}
|
||||
if (range_tombstone_iter_) {
|
||||
if (*range_tombstone_iter_) {
|
||||
(*range_tombstone_iter_)->SeekToFirst();
|
||||
@@ -1536,16 +1661,32 @@ void LevelIterator::SkipEmptyFileBackward() {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool LevelIterator::OverlapRange(const ScanOptions& opts) {
|
||||
return (user_comparator_.CompareWithoutTimestamp(
|
||||
opts.range.start.value(), /*a_has_ts=*/false,
|
||||
ExtractUserKey(flevel_->files[file_index_].largest_key),
|
||||
/*b_has_ts=*/true) <= 0 &&
|
||||
user_comparator_.CompareWithoutTimestamp(
|
||||
opts.range.limit.value(), /*a_has_ts=*/false,
|
||||
ExtractUserKey(flevel_->files[file_index_].smallest_key),
|
||||
/*b_has_ts=*/true) > 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
void LevelIterator::SetFileIterator(InternalIterator* iter) {
|
||||
if (pinned_iters_mgr_ && iter) {
|
||||
iter->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
InternalIterator* old_iter = file_iter_.Set(iter);
|
||||
// Since this is a new table iterator, no need to call Prepare() if
|
||||
// scan_opts_ is null
|
||||
if (iter && scan_opts_) {
|
||||
file_iter_.Prepare(scan_opts_);
|
||||
if (FileHasMultiScanArg(file_index_)) {
|
||||
const MultiScanArgs& new_opts = GetMultiScanArgForFile(file_index_);
|
||||
assert(OverlapRange(*new_opts.GetScanRanges().begin()) &&
|
||||
OverlapRange(*new_opts.GetScanRanges().rbegin()));
|
||||
file_iter_.Prepare(&new_opts);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the read pattern for PrefetchBuffer.
|
||||
@@ -1582,6 +1723,7 @@ void LevelIterator::InitFileIterator(size_t new_file_index) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
Status Version::GetTableProperties(const ReadOptions& read_options,
|
||||
@@ -2741,9 +2883,10 @@ void Version::MultiGet(const ReadOptions& read_options, MultiGetRange* range,
|
||||
RecordTick(db_statistics_, MULTIGET_COROUTINE_COUNT,
|
||||
mget_tasks.size());
|
||||
// Collect all results so far
|
||||
std::vector<Status> statuses = folly::coro::blockingWait(
|
||||
folly::coro::collectAllRange(std::move(mget_tasks))
|
||||
.scheduleOn(&range->context()->executor()));
|
||||
std::vector<Status> statuses =
|
||||
folly::coro::blockingWait(co_withExecutor(
|
||||
&range->context()->executor(),
|
||||
folly::coro::collectAllRange(std::move(mget_tasks))));
|
||||
if (s.ok()) {
|
||||
for (Status stat : statuses) {
|
||||
if (!stat.ok()) {
|
||||
@@ -3028,9 +3171,10 @@ Status Version::MultiGetAsync(
|
||||
assert(waiting.size());
|
||||
RecordTick(db_statistics_, MULTIGET_COROUTINE_COUNT, mget_tasks.size());
|
||||
// Collect all results so far
|
||||
std::vector<Status> statuses = folly::coro::blockingWait(
|
||||
folly::coro::collectAllRange(std::move(mget_tasks))
|
||||
.scheduleOn(&range->context()->executor()));
|
||||
std::vector<Status> statuses =
|
||||
folly::coro::blockingWait(co_withExecutor(
|
||||
&range->context()->executor(),
|
||||
folly::coro::collectAllRange(std::move(mget_tasks))));
|
||||
mget_tasks.clear();
|
||||
if (s.ok()) {
|
||||
for (Status stat : statuses) {
|
||||
@@ -3466,7 +3610,9 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
// maintaining it to be over 1.0, we scale the original score by 10x
|
||||
// if it is larger than 1.0.
|
||||
const double kScoreScale = 10.0;
|
||||
int max_output_level = MaxOutputLevel(immutable_options.allow_ingest_behind);
|
||||
int max_output_level =
|
||||
MaxOutputLevel(immutable_options.cf_allow_ingest_behind ||
|
||||
immutable_options.allow_ingest_behind);
|
||||
for (int level = 0; level <= MaxInputLevel(); level++) {
|
||||
double score;
|
||||
if (level == 0) {
|
||||
@@ -3507,8 +3653,13 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
}
|
||||
|
||||
if (compaction_style_ == kCompactionStyleFIFO) {
|
||||
score = static_cast<double>(total_size) /
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size;
|
||||
auto max_table_files_size =
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size;
|
||||
if (max_table_files_size == 0) {
|
||||
// avoid divide 0
|
||||
max_table_files_size = 1;
|
||||
}
|
||||
score = static_cast<double>(total_size) / max_table_files_size;
|
||||
if (score < 1 &&
|
||||
mutable_cf_options.compaction_options_fifo.allow_compaction) {
|
||||
score = std::max(
|
||||
@@ -3646,6 +3797,7 @@ void VersionStorageInfo::ComputeCompactionScore(
|
||||
}
|
||||
ComputeFilesMarkedForCompaction(max_output_level);
|
||||
ComputeBottommostFilesMarkedForCompaction(
|
||||
immutable_options.cf_allow_ingest_behind ||
|
||||
immutable_options.allow_ingest_behind);
|
||||
ComputeExpiredTtlFiles(immutable_options, mutable_cf_options.ttl);
|
||||
ComputeFilesMarkedForPeriodicCompaction(
|
||||
@@ -4643,8 +4795,7 @@ void VersionStorageInfo::RecoverEpochNumbers(ColumnFamilyData* cfd,
|
||||
if (restart_epoch) {
|
||||
cfd->ResetNextEpochNumber();
|
||||
|
||||
bool reserve_epoch_num_for_file_ingested_behind =
|
||||
cfd->ioptions().allow_ingest_behind;
|
||||
bool reserve_epoch_num_for_file_ingested_behind = cfd->AllowIngestBehind();
|
||||
if (reserve_epoch_num_for_file_ingested_behind) {
|
||||
uint64_t reserved_epoch_number = cfd->NewEpochNumber();
|
||||
assert(reserved_epoch_number ==
|
||||
@@ -4652,7 +4803,8 @@ void VersionStorageInfo::RecoverEpochNumbers(ColumnFamilyData* cfd,
|
||||
ROCKS_LOG_INFO(cfd->ioptions().info_log.get(),
|
||||
"[%s]CF has reserved epoch number %" PRIu64
|
||||
" for files ingested "
|
||||
"behind since `Options::allow_ingest_behind` is true",
|
||||
"behind since `Options::allow_ingest_behind` or "
|
||||
"`Options::cf_allow_ingest_behind` is true",
|
||||
cfd->GetName().c_str(), reserved_epoch_number);
|
||||
}
|
||||
}
|
||||
@@ -6421,7 +6573,7 @@ Status VersionSet::ReduceNumberOfLevels(const std::string& dbname,
|
||||
nullptr /*BlockCacheTracer*/, nullptr /*IOTracer*/,
|
||||
/*db_id*/ "",
|
||||
/*db_session_id*/ "", options->daily_offpeak_time_utc,
|
||||
/*error_handler_*/ nullptr, /*read_only=*/false);
|
||||
/*error_handler_*/ nullptr, /*unchanging=*/false);
|
||||
Status status;
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> dummy;
|
||||
|
||||
@@ -669,6 +669,8 @@ class VersionStorageInfo {
|
||||
|
||||
// List of files per level, files in each level are arranged
|
||||
// in increasing order of keys
|
||||
// In L0, files are ordered in decreasing epoch number, meaning
|
||||
// more recent updates are ordered first.
|
||||
std::vector<FileMetaData*>* files_;
|
||||
|
||||
// Map of all table files in version. Maps file number to (level, position on
|
||||
|
||||
@@ -2,13 +2,13 @@ add_executable(db_stress${ARTIFACT_SUFFIX}
|
||||
batched_ops_stress.cc
|
||||
cf_consistency_stress.cc
|
||||
db_stress.cc
|
||||
db_stress_compression_manager.cc
|
||||
db_stress_common.cc
|
||||
db_stress_driver.cc
|
||||
db_stress_filters.cc
|
||||
db_stress_gflags.cc
|
||||
db_stress_listener.cc
|
||||
db_stress_shared_state.cc
|
||||
db_stress_stat.cc
|
||||
db_stress_test_base.cc
|
||||
db_stress_wide_merge_operator.cc
|
||||
db_stress_tool.cc
|
||||
|
||||
@@ -228,6 +228,66 @@ void CompressedCacheSetCapacityThread(void* v) {
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteCompactionWorkerThread(void* v) {
|
||||
assert(FLAGS_remote_compaction_worker_threads > 0);
|
||||
assert(FLAGS_remote_compaction_worker_interval > 0);
|
||||
auto* thread = static_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
StressTest* stress_test = shared->GetStressTest();
|
||||
assert(stress_test != nullptr);
|
||||
while (true) {
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
if (shared->ShouldStopBgThread()) {
|
||||
shared->IncBgThreadsFinished();
|
||||
if (shared->BgThreadsFinished()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::string job_id;
|
||||
CompactionServiceJobInfo job_info;
|
||||
std::string serialized_input;
|
||||
if (shared->DequeueRemoteCompaction(&job_id, &job_info,
|
||||
&serialized_input)) {
|
||||
auto options = stress_test->GetOptions(job_info.cf_id);
|
||||
CompactionServiceOptionsOverride override_options{
|
||||
.file_checksum_gen_factory = options.file_checksum_gen_factory,
|
||||
.merge_operator = options.merge_operator,
|
||||
.compaction_filter = options.compaction_filter,
|
||||
.compaction_filter_factory = options.compaction_filter_factory,
|
||||
.prefix_extractor = options.prefix_extractor,
|
||||
.table_factory = options.table_factory,
|
||||
.sst_partitioner_factory = options.sst_partitioner_factory,
|
||||
.listeners = {},
|
||||
.statistics = options.statistics,
|
||||
.table_properties_collector_factories =
|
||||
options.table_properties_collector_factories};
|
||||
std::string tmp_output_dir = job_info.db_name + "/" + "tmp_output_" +
|
||||
db_stress_env->GenerateUniqueId();
|
||||
std::string serialized_output;
|
||||
Status s = DB::OpenAndCompact(OpenAndCompactOptions{}, job_info.db_name,
|
||||
tmp_output_dir, serialized_input,
|
||||
&serialized_output, override_options);
|
||||
if (!s.ok()) {
|
||||
// Print in stdout instead of stderr to avoid stress test failure,
|
||||
// because OpenAndCompact() failure doesn't necessarily mean
|
||||
// primary db instance failure.
|
||||
fprintf(stdout, "Failed to run OpenAndCompact(%s): %s\n",
|
||||
job_info.db_name.c_str(), s.ToString().c_str());
|
||||
}
|
||||
// Add the output regardless of status, so that primary DB doesn't rely on
|
||||
// the timeout to finish waiting. The actual failure from the
|
||||
// deserialization can fail the compaction properly
|
||||
shared->AddRemoteCompactionResult(job_id, serialized_output);
|
||||
}
|
||||
db_stress_env->SleepForMicroseconds(
|
||||
thread->rand.Next() % FLAGS_remote_compaction_worker_interval * 1000 +
|
||||
1);
|
||||
}
|
||||
}
|
||||
|
||||
void PrintKeyValue(int cf, uint64_t key, const char* value, size_t sz) {
|
||||
if (!FLAGS_verbose) {
|
||||
return;
|
||||
|
||||
@@ -275,6 +275,7 @@ DECLARE_string(last_level_temperature);
|
||||
DECLARE_string(default_write_temperature);
|
||||
DECLARE_string(default_temperature);
|
||||
DECLARE_bool(paranoid_memory_checks);
|
||||
DECLARE_bool(memtable_veirfy_per_key_checksum_on_seek);
|
||||
|
||||
// Options for transaction dbs.
|
||||
// Use TransactionDB (a.k.a. Pessimistic Transaction DB)
|
||||
@@ -284,6 +285,7 @@ DECLARE_bool(use_txn);
|
||||
// Options for TransactionDB (a.k.a. Pessimistic Transaction DB)
|
||||
DECLARE_uint64(txn_write_policy);
|
||||
DECLARE_bool(unordered_write);
|
||||
DECLARE_bool(use_per_key_point_lock_mgr);
|
||||
|
||||
// Options for OptimisticTransactionDB
|
||||
DECLARE_bool(use_optimistic_txn);
|
||||
@@ -422,12 +424,23 @@ DECLARE_string(file_temperature_age_thresholds);
|
||||
DECLARE_bool(allow_trivial_copy_when_change_temperature);
|
||||
DECLARE_uint32(commit_bypass_memtable_one_in);
|
||||
DECLARE_bool(track_and_verify_wals);
|
||||
DECLARE_bool(enable_remote_compaction);
|
||||
DECLARE_int32(remote_compaction_worker_threads);
|
||||
DECLARE_int32(remote_compaction_worker_interval);
|
||||
DECLARE_bool(remote_compaction_failure_fall_back_to_local);
|
||||
DECLARE_bool(auto_refresh_iterator_with_snapshot);
|
||||
DECLARE_uint32(memtable_op_scan_flush_trigger);
|
||||
DECLARE_uint32(memtable_avg_op_scan_flush_trigger);
|
||||
DECLARE_uint32(ingest_wbwi_one_in);
|
||||
DECLARE_bool(universal_reduce_file_locking);
|
||||
DECLARE_bool(use_multiscan);
|
||||
DECLARE_bool(multiscan_use_async_io);
|
||||
|
||||
// Compaction deletion trigger declarations for stress testing
|
||||
DECLARE_bool(enable_compaction_on_deletion_trigger);
|
||||
DECLARE_uint64(compaction_on_deletion_min_file_size);
|
||||
DECLARE_int32(compaction_on_deletion_trigger_count);
|
||||
DECLARE_int32(compaction_on_deletion_window_size);
|
||||
DECLARE_double(compaction_on_deletion_ratio);
|
||||
|
||||
constexpr long KB = 1024;
|
||||
constexpr int kRandomValueMaxFactor = 3;
|
||||
@@ -755,6 +768,8 @@ void PoolSizeChangeThread(void* v);
|
||||
|
||||
void DbVerificationThread(void* v);
|
||||
|
||||
void RemoteCompactionWorkerThread(void* v);
|
||||
|
||||
void CompressedCacheSetCapacityThread(void* v);
|
||||
|
||||
void TimestampedSnapshotsThread(void* v);
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "db_stress_shared_state.h"
|
||||
#include "db_stress_tool/db_stress_common.h"
|
||||
#include "rocksdb/options.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -12,28 +14,91 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// Service to simulate Remote Compaction in Stress Test
|
||||
class DbStressCompactionService : public CompactionService {
|
||||
public:
|
||||
explicit DbStressCompactionService() {}
|
||||
explicit DbStressCompactionService(SharedState* shared,
|
||||
bool failure_should_fall_back_to_local)
|
||||
: shared_(shared),
|
||||
aborted_(false),
|
||||
failure_should_fall_back_to_local_(failure_should_fall_back_to_local) {}
|
||||
|
||||
static const char* kClassName() { return "DbStressCompactionService"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
static constexpr uint64_t kWaitIntervalInMicros = 10 * 1000; // 10ms
|
||||
static constexpr uint64_t kWaitTimeoutInMicros =
|
||||
30 * 1000 * 1000; // 30 seconds
|
||||
|
||||
CompactionServiceScheduleResponse Schedule(
|
||||
const CompactionServiceJobInfo& /*info*/,
|
||||
const std::string& /*compaction_service_input*/) override {
|
||||
const CompactionServiceJobInfo& info,
|
||||
const std::string& compaction_service_input) override {
|
||||
std::string job_id = info.db_id + "_" + info.db_session_id + "_" +
|
||||
std::to_string(info.job_id);
|
||||
if (aborted_.load()) {
|
||||
return CompactionServiceScheduleResponse(
|
||||
job_id, CompactionServiceJobStatus::kUseLocal);
|
||||
}
|
||||
shared_->EnqueueRemoteCompaction(job_id, info, compaction_service_input);
|
||||
CompactionServiceScheduleResponse response(
|
||||
"Implement Me", CompactionServiceJobStatus::kUseLocal);
|
||||
job_id, CompactionServiceJobStatus::kSuccess);
|
||||
return response;
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus Wait(const std::string& /*scheduled_job_id*/,
|
||||
std::string* /*result*/) override {
|
||||
// TODO - Implement
|
||||
return CompactionServiceJobStatus::kUseLocal;
|
||||
CompactionServiceJobStatus Wait(const std::string& scheduled_job_id,
|
||||
std::string* result) override {
|
||||
auto start = Env::Default()->NowMicros();
|
||||
while (Env::Default()->NowMicros() - start < kWaitTimeoutInMicros) {
|
||||
if (aborted_.load()) {
|
||||
return CompactionServiceJobStatus::kUseLocal;
|
||||
}
|
||||
if (shared_->GetRemoteCompactionResult(scheduled_job_id, result).ok()) {
|
||||
if (result && result->empty()) {
|
||||
// Race: Remote worker aborted before client sets aborted_ = true
|
||||
return CompactionServiceJobStatus::kUseLocal;
|
||||
}
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
}
|
||||
Env::Default()->SleepForMicroseconds(kWaitIntervalInMicros);
|
||||
}
|
||||
if (failure_should_fall_back_to_local_) {
|
||||
fprintf(stdout,
|
||||
"Remote Compaction failed - fall back to local compaction!\n");
|
||||
return CompactionServiceJobStatus::kUseLocal;
|
||||
}
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
// TODO - Implement
|
||||
void CancelAwaitingJobs() override {}
|
||||
void OnInstallation(const std::string& scheduled_job_id,
|
||||
CompactionServiceJobStatus /*status*/) override {
|
||||
// Clean up tmp directory
|
||||
std::string serialized;
|
||||
CompactionServiceResult result;
|
||||
if (shared_->GetRemoteCompactionResult(scheduled_job_id, &serialized)
|
||||
.ok()) {
|
||||
if (CompactionServiceResult::Read(serialized, &result).ok()) {
|
||||
std::vector<std::string> filenames;
|
||||
Status s = Env::Default()->GetChildren(result.output_path, &filenames);
|
||||
for (size_t i = 0; s.ok() && i < filenames.size(); ++i) {
|
||||
s = Env::Default()->DeleteFile(result.output_path + "/" +
|
||||
filenames[i]);
|
||||
if (!s.ok()) {
|
||||
// TODO - Handle clean up failure?
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
Env::Default()->DeleteDir(result.output_path).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
shared_->RemoveRemoteCompactionResult(scheduled_job_id);
|
||||
}
|
||||
}
|
||||
|
||||
void CancelAwaitingJobs() override { aborted_.store(true); }
|
||||
|
||||
private:
|
||||
SharedState* shared_;
|
||||
std::atomic_bool aborted_{false};
|
||||
bool failure_should_fall_back_to_local_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
#include "db_stress_compression_manager.h"
|
||||
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
void DbStressCustomCompressionManager::Register() {
|
||||
// We must register any compression managers with a custom
|
||||
// CompatibilityName() so that if it was used in a past invocation but not
|
||||
// the current invocation, we can still read the SST files requiring it.
|
||||
static std::once_flag loaded;
|
||||
std::call_once(loaded, [&]() {
|
||||
TEST_AllowUnsupportedFormatVersion() = true;
|
||||
auto& library = *ObjectLibrary::Default();
|
||||
library.AddFactory<CompressionManager>(
|
||||
DbStressCustomCompressionManager().CompatibilityName(),
|
||||
[](const std::string& /*uri*/,
|
||||
std::unique_ptr<CompressionManager>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
*guard = std::make_unique<DbStressCustomCompressionManager>();
|
||||
return guard->get();
|
||||
});
|
||||
});
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -57,6 +57,8 @@ class DbStressCustomCompressionManager : public CompressionManager {
|
||||
return decomp;
|
||||
}
|
||||
|
||||
static void Register();
|
||||
|
||||
protected:
|
||||
std::shared_ptr<CompressionManager> default_ =
|
||||
GetBuiltinV2CompressionManager();
|
||||
|
||||
@@ -102,6 +102,14 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
shared->IncBgThreads();
|
||||
}
|
||||
|
||||
uint32_t remote_compaction_worker_thread_count =
|
||||
FLAGS_remote_compaction_worker_threads;
|
||||
if (remote_compaction_worker_thread_count > 0) {
|
||||
for (uint32_t i = 0; i < remote_compaction_worker_thread_count; i++) {
|
||||
shared->IncBgThreads();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ThreadState*> threads(n);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
threads[i] = new ThreadState(i, shared);
|
||||
@@ -126,6 +134,17 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
&compressed_cache_set_capacity_thread);
|
||||
}
|
||||
|
||||
std::vector<ThreadState*> remote_compaction_worker_threads;
|
||||
if (remote_compaction_worker_thread_count > 0) {
|
||||
remote_compaction_worker_threads.reserve(
|
||||
remote_compaction_worker_thread_count);
|
||||
for (uint32_t i = 0; i < remote_compaction_worker_thread_count; i++) {
|
||||
ThreadState* ts = new ThreadState(i, shared);
|
||||
remote_compaction_worker_threads.push_back(ts);
|
||||
db_stress_env->StartThread(RemoteCompactionWorkerThread, ts);
|
||||
}
|
||||
}
|
||||
|
||||
// Each thread goes through the following states:
|
||||
// initializing -> wait for others to init -> read/populate/depopulate
|
||||
// wait for others to operate -> verify -> done
|
||||
@@ -218,6 +237,7 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
delete threads[i];
|
||||
threads[i] = nullptr;
|
||||
}
|
||||
|
||||
now = clock->NowMicros();
|
||||
if (!FLAGS_skip_verifydb && !FLAGS_test_batches_snapshots &&
|
||||
!shared->HasVerificationFailedYet()) {
|
||||
@@ -232,7 +252,8 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
if (FLAGS_compaction_thread_pool_adjust_interval > 0 ||
|
||||
FLAGS_continuous_verification_interval > 0 ||
|
||||
FLAGS_compressed_secondary_cache_size > 0 ||
|
||||
FLAGS_compressed_secondary_cache_ratio > 0.0) {
|
||||
FLAGS_compressed_secondary_cache_ratio > 0.0 ||
|
||||
remote_compaction_worker_thread_count > 0) {
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->SetShouldStopBgThread();
|
||||
while (!shared->BgThreadsFinished()) {
|
||||
@@ -240,6 +261,15 @@ bool RunStressTestImpl(SharedState* shared) {
|
||||
}
|
||||
}
|
||||
|
||||
assert(remote_compaction_worker_threads.size() ==
|
||||
remote_compaction_worker_thread_count);
|
||||
if (remote_compaction_worker_thread_count > 0) {
|
||||
for (ThreadState* thread_state : remote_compaction_worker_threads) {
|
||||
delete thread_state;
|
||||
}
|
||||
remote_compaction_worker_threads.clear();
|
||||
}
|
||||
|
||||
if (shared->HasVerificationFailedYet()) {
|
||||
fprintf(stderr, "Verification failed :(\n");
|
||||
return false;
|
||||
|
||||
Loaded 100 of 285 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user