mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 19:12:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf2c335184 | ||
|
|
7aa573eb45 | ||
|
|
e0cf5cd5f3 | ||
|
|
7ad900e1fb | ||
|
|
491615ccaa | ||
|
|
1cf453943d | ||
|
|
442b6b8048 | ||
|
|
8c06988313 | ||
|
|
8d5edb693c | ||
|
|
32d853f91c | ||
|
|
e0b1793c2f | ||
|
|
f690f2449a | ||
|
|
c097b01467 | ||
|
|
6b2f41f223 | ||
|
|
d53da91ec2 | ||
|
|
0ffd94d615 | ||
|
|
fb7b420443 | ||
|
|
3453a88b57 | ||
|
|
49b7f219de | ||
|
|
0d82c62664 | ||
|
|
3ecef275be | ||
|
|
9e0c4a0aa7 |
No files matched your search
+117
-93
@@ -1,7 +1,7 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
win: circleci/windows@5.0.0
|
||||
win: circleci/windows@2.4.0
|
||||
|
||||
commands:
|
||||
install-cmake-on-macos:
|
||||
@@ -45,38 +45,6 @@ commands:
|
||||
echo "export LZ4_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/lz4" >> $BASH_ENV
|
||||
echo "export ZSTD_DOWNLOAD_BASE=https://rocksdb-deps.s3.us-west-2.amazonaws.com/pkgs/zstd" >> $BASH_ENV
|
||||
|
||||
windows-build-steps:
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: "Install thirdparty dependencies"
|
||||
command: |
|
||||
echo "Installing CMake..."
|
||||
choco install cmake --installargs 'ADD_CMAKE_TO_PATH=System' -y
|
||||
mkdir $Env:THIRDPARTY_HOME
|
||||
cd $Env:THIRDPARTY_HOME
|
||||
echo "Building Snappy dependency..."
|
||||
curl https://github.com/google/snappy/archive/refs/tags/1.1.8.zip -O snappy-1.1.8.zip
|
||||
unzip -q snappy-1.1.8.zip
|
||||
cd snappy-1.1.8
|
||||
mkdir build
|
||||
cd build
|
||||
& $Env:CMAKE_BIN -G "$Env:CMAKE_GENERATOR" ..
|
||||
msbuild.exe Snappy.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
- run:
|
||||
name: "Build RocksDB"
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
& $Env:CMAKE_BIN -G "$Env:CMAKE_GENERATOR" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DJNI=1 ..
|
||||
cd ..
|
||||
echo "Building with VS version: $Env:CMAKE_GENERATOR"
|
||||
msbuild.exe build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
- run:
|
||||
name: "Test RocksDB"
|
||||
shell: powershell.exe
|
||||
command: |
|
||||
build_tools\run_ci_db_test.ps1 -SuiteRun arena_test,db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16
|
||||
pre-steps-macos:
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -152,15 +120,15 @@ commands:
|
||||
steps:
|
||||
- run:
|
||||
name: "Test low-variance benchmarks"
|
||||
command: ./tools/benchmark_ci.py --db_dir /tmp/rocksdb-benchmark-datadir --output_dir /tmp/benchmark-results --num_keys 20000000
|
||||
command: ./tools/benchmark_ci.py --db_dir /tmp/rocksdb-benchmark-datadir --output_dir /tmp/benchmark-results --num_keys 10000000
|
||||
environment:
|
||||
LD_LIBRARY_PATH: /usr/local/lib
|
||||
# How long to run parts of the test(s)
|
||||
DURATION_RO: 300
|
||||
DURATION_RW: 500
|
||||
DURATION_RO: 400
|
||||
DURATION_RW: 700
|
||||
# Keep threads within physical capacity of server (much lower than default)
|
||||
NUM_THREADS: 1
|
||||
MAX_BACKGROUND_JOBS: 4
|
||||
MAX_BACKGROUND_JOBS: 3
|
||||
# Don't run a couple of "optional" initial tests
|
||||
CI_TESTS_ONLY: "true"
|
||||
# Reduce configured size of levels to ensure more levels in the leveled compaction LSM tree
|
||||
@@ -170,11 +138,7 @@ commands:
|
||||
# The benchmark host has 32GB memory
|
||||
# The following values are tailored to work with that
|
||||
# Note, tests may not exercise the targeted issues if the memory is increased on new test hosts.
|
||||
COMPRESSION_TYPE: "none"
|
||||
CACHE_INDEX_AND_FILTER_BLOCKS: 1
|
||||
MIN_LEVEL_TO_COMPRESS: 3
|
||||
CACHE_SIZE_MB: 10240
|
||||
MB_WRITE_PER_SEC: 2
|
||||
|
||||
|
||||
post-benchmarks:
|
||||
steps:
|
||||
@@ -190,6 +154,11 @@ commands:
|
||||
true
|
||||
|
||||
executors:
|
||||
windows-2xlarge:
|
||||
machine:
|
||||
image: 'windows-server-2019-vs2019:stable'
|
||||
resource_class: windows.2xlarge
|
||||
shell: bash.exe
|
||||
linux-docker:
|
||||
docker:
|
||||
# The image configuration is build_tools/ubuntu20_image/Dockerfile
|
||||
@@ -273,12 +242,12 @@ jobs:
|
||||
./sst_dump --help | grep -E -q 'Supported compression types: kNoCompression$' # Verify no compiled in compression
|
||||
- post-steps
|
||||
|
||||
build-linux-static_lib-alt_namespace-status_checked:
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
executor: linux-docker
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- 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=shared OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j32 check
|
||||
- post-steps
|
||||
|
||||
build-linux-release:
|
||||
@@ -286,21 +255,11 @@ jobs:
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: make V=1 -j32 LIB_MODE=shared release
|
||||
- run: ls librocksdb.so # ensure shared lib built
|
||||
- run: ./db_stress --version # ensure with gflags
|
||||
- run: make clean
|
||||
- run: make V=1 -j32 release
|
||||
- run: ls librocksdb.a # ensure static lib built
|
||||
- run: ./db_stress --version # ensure with gflags
|
||||
- run: make clean
|
||||
- run: apt-get remove -y libgflags-dev
|
||||
- run: make V=1 -j32 LIB_MODE=shared release
|
||||
- run: ls librocksdb.so # ensure shared lib built
|
||||
- run: if ./db_stress --version; then false; else true; fi # ensure without gflags
|
||||
- run: make clean
|
||||
- run: make V=1 -j32 release
|
||||
- run: ls librocksdb.a # ensure static lib built
|
||||
- run: if ./db_stress --version; then false; else true; fi # ensure without gflags
|
||||
- post-steps
|
||||
|
||||
@@ -316,6 +275,27 @@ jobs:
|
||||
- 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 # ensure without gflags
|
||||
|
||||
build-linux-lite:
|
||||
executor: linux-docker
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: LITE=1 make V=1 J=8 -j8 check
|
||||
- post-steps
|
||||
|
||||
build-linux-lite-release:
|
||||
executor: linux-docker
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: LITE=1 make V=1 -j8 release
|
||||
- run: ./db_stress --version # ensure with gflags
|
||||
- run: make clean
|
||||
- run: apt-get remove -y libgflags-dev
|
||||
- run: LITE=1 make V=1 -j8 release
|
||||
- run: if ./db_stress --version; then false; else true; fi # ensure without gflags
|
||||
- post-steps
|
||||
|
||||
build-linux-clang-no_test_run:
|
||||
executor: linux-docker
|
||||
resource_class: xlarge
|
||||
@@ -420,10 +400,7 @@ jobs:
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: apt-get update -y && apt-get install -y libgflags-dev
|
||||
- run:
|
||||
name: "Unity build"
|
||||
command: make V=1 -j8 unity_test
|
||||
no_output_timeout: 20m
|
||||
- run: make V=1 -j8 unity_test
|
||||
- run: make V=1 -j8 -k check-headers # could be moved to a different build
|
||||
- post-steps
|
||||
|
||||
@@ -434,7 +411,7 @@ jobs:
|
||||
- pre-steps
|
||||
- setup-folly
|
||||
- build-folly
|
||||
- run: USE_FOLLY=1 LIB_MODE=static CC=gcc-7 CXX=g++-7 V=1 make -j32 check # TODO: LIB_MODE only to work around unresolved linker failures
|
||||
- run: USE_FOLLY=1 CC=gcc-7 CXX=g++-7 V=1 make -j32 check
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-7-with-folly-lite-no-test:
|
||||
@@ -480,7 +457,7 @@ jobs:
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: LIB_MODE=static CC=gcc-11 CXX=g++-11 V=1 make -j32 all microbench # TODO: LIB_MODE only to work around unresolved linker failures
|
||||
- run: CC=gcc-11 CXX=g++-11 V=1 make -j32 all microbench
|
||||
- post-steps
|
||||
|
||||
build-linux-clang-13-no_test_run:
|
||||
@@ -499,7 +476,7 @@ jobs:
|
||||
- pre-steps
|
||||
- setup-folly
|
||||
- build-folly
|
||||
- run: CC=clang-13 CXX=clang++-13 LIB_MODE=static USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check # TODO: LIB_MODE only to work around unresolved linker failures
|
||||
- run: CC=clang-13 CXX=clang++-13 USE_CLANG=1 USE_FOLLY=1 COMPILE_WITH_UBSAN=1 COMPILE_WITH_ASAN=1 make -j32 check
|
||||
- post-steps
|
||||
|
||||
# This job is only to make sure the microbench tests are able to run, the benchmark result is not meaningful as the CI host is changing.
|
||||
@@ -516,7 +493,7 @@ jobs:
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: ulimit -S -n `ulimit -H -n` && make V=1 -j8 CRASH_TEST_EXT_ARGS='--duration=960 --max_key=2500000 --use_io_uring=0' blackbox_crash_test_with_atomic_flush
|
||||
- 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
|
||||
- post-steps
|
||||
|
||||
build-linux-crashtest-tiered-storage-bb:
|
||||
@@ -526,7 +503,7 @@ jobs:
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "run crashtest"
|
||||
command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS='--duration=10800 --use_io_uring=0' blackbox_crash_test_with_tiered_storage
|
||||
command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS=--duration=10800 blackbox_crash_test_with_tiered_storage
|
||||
no_output_timeout: 100m
|
||||
- post-steps
|
||||
|
||||
@@ -537,39 +514,77 @@ jobs:
|
||||
- pre-steps
|
||||
- run:
|
||||
name: "run crashtest"
|
||||
command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS='--duration=10800 --use_io_uring=0' whitebox_crash_test_with_tiered_storage
|
||||
command: ulimit -S -n `ulimit -H -n` && make V=1 -j32 CRASH_TEST_EXT_ARGS=--duration=10800 whitebox_crash_test_with_tiered_storage
|
||||
no_output_timeout: 100m
|
||||
- post-steps
|
||||
|
||||
build-windows-vs2022:
|
||||
executor:
|
||||
name: win/server-2022
|
||||
size: 2xlarge
|
||||
build-windows:
|
||||
executor: windows-2xlarge
|
||||
parameters:
|
||||
extra_cmake_opt:
|
||||
default: ""
|
||||
type: string
|
||||
vs_year:
|
||||
default: "2019"
|
||||
type: string
|
||||
cmake_generator:
|
||||
default: "Visual Studio 16 2019"
|
||||
type: string
|
||||
environment:
|
||||
THIRDPARTY_HOME: C:/Users/circleci/thirdparty
|
||||
CMAKE_HOME: C:/Program Files/CMake
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
CMAKE_GENERATOR: Visual Studio 17 2022
|
||||
CMAKE_HOME: C:/Users/circleci/thirdparty/cmake-3.16.4-win64-x64
|
||||
CMAKE_BIN: C:/Users/circleci/thirdparty/cmake-3.16.4-win64-x64/bin/cmake.exe
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.7
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.7;C:/Users/circleci/thirdparty/snappy-1.1.7/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.7/build/Debug/snappy.lib
|
||||
VS_YEAR: <<parameters.vs_year>>
|
||||
CMAKE_GENERATOR: <<parameters.cmake_generator>>
|
||||
steps:
|
||||
- windows-build-steps
|
||||
|
||||
build-windows-vs2019:
|
||||
executor:
|
||||
name: win/server-2019
|
||||
size: 2xlarge
|
||||
environment:
|
||||
THIRDPARTY_HOME: C:/Users/circleci/thirdparty
|
||||
CMAKE_HOME: C:/Program Files/CMake
|
||||
CMAKE_BIN: C:/Program Files/CMake/bin/cmake.exe
|
||||
SNAPPY_HOME: C:/Users/circleci/thirdparty/snappy-1.1.8
|
||||
SNAPPY_INCLUDE: C:/Users/circleci/thirdparty/snappy-1.1.8;C:/Users/circleci/thirdparty/snappy-1.1.8/build
|
||||
SNAPPY_LIB_DEBUG: C:/Users/circleci/thirdparty/snappy-1.1.8/build/Debug/snappy.lib
|
||||
CMAKE_GENERATOR: Visual Studio 16 2019
|
||||
steps:
|
||||
- windows-build-steps
|
||||
- checkout
|
||||
- run:
|
||||
name: "Setup VS"
|
||||
command: |
|
||||
if [[ "${VS_YEAR}" == "2019" ]]; then
|
||||
echo "VS2019 already present."
|
||||
elif [[ "${VS_YEAR}" == "2017" ]]; then
|
||||
echo "Installing VS2017..."
|
||||
powershell .circleci/vs2017_install.ps1
|
||||
elif [[ "${VS_YEAR}" == "2015" ]]; then
|
||||
echo "Installing VS2015..."
|
||||
powershell .circleci/vs2015_install.ps1
|
||||
fi
|
||||
- store_artifacts:
|
||||
path: \Users\circleci\AppData\Local\Temp\vslogs.zip
|
||||
- run:
|
||||
name: "Install thirdparty dependencies"
|
||||
command: |
|
||||
mkdir ${THIRDPARTY_HOME}
|
||||
cd ${THIRDPARTY_HOME}
|
||||
echo "Installing CMake..."
|
||||
curl --fail --silent --show-error --output cmake-3.16.4-win64-x64.zip --location https://github.com/Kitware/CMake/releases/download/v3.16.4/cmake-3.16.4-win64-x64.zip
|
||||
unzip -q cmake-3.16.4-win64-x64.zip
|
||||
echo "Building Snappy dependency..."
|
||||
curl --fail --silent --show-error --output snappy-1.1.7.zip --location https://github.com/google/snappy/archive/1.1.7.zip
|
||||
unzip -q snappy-1.1.7.zip
|
||||
cd snappy-1.1.7
|
||||
mkdir build
|
||||
cd build
|
||||
${CMAKE_BIN} -G "${CMAKE_GENERATOR}" ..
|
||||
msbuild.exe Snappy.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
- run:
|
||||
name: "Build RocksDB"
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
${CMAKE_BIN} -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DJNI=1 << parameters.extra_cmake_opt >> ..
|
||||
cd ..
|
||||
echo "Building with VS version: ${CMAKE_GENERATOR}"
|
||||
msbuild.exe build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
- run:
|
||||
name: "Test RocksDB"
|
||||
shell: powershell.exe
|
||||
command: |
|
||||
build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test,db_test2,db_merge_operand_test,bloom_test,c_test,coding_test,crc32c_test,dynamic_bloom_test,env_basic_test,env_test,hash_test,random_test -Concurrency 16
|
||||
|
||||
build-linux-java:
|
||||
executor: linux-docker
|
||||
@@ -817,16 +832,18 @@ workflows:
|
||||
- build-linux-cmake-with-folly-coroutines
|
||||
- build-linux-cmake-with-benchmark
|
||||
- build-linux-encrypted_env-no_compression
|
||||
- build-linux-lite
|
||||
jobs-linux-run-tests-san:
|
||||
jobs:
|
||||
- build-linux-clang10-asan
|
||||
- build-linux-clang10-ubsan
|
||||
- build-linux-clang10-mini-tsan
|
||||
- build-linux-static_lib-alt_namespace-status_checked
|
||||
- build-linux-shared_lib-alt_namespace-status_checked
|
||||
jobs-linux-no-test-run:
|
||||
jobs:
|
||||
- build-linux-release
|
||||
- build-linux-release-rtti
|
||||
- build-linux-lite-release
|
||||
- build-examples
|
||||
- build-fuzzers
|
||||
- build-linux-clang-no_test_run
|
||||
@@ -842,8 +859,15 @@ workflows:
|
||||
- build-linux-mini-crashtest
|
||||
jobs-windows:
|
||||
jobs:
|
||||
- build-windows-vs2022
|
||||
- build-windows-vs2019
|
||||
- build-windows:
|
||||
name: "build-windows-vs2019"
|
||||
- build-windows:
|
||||
name: "build-windows-vs2019-cxx20"
|
||||
extra_cmake_opt: -DCMAKE_CXX_STANDARD=20
|
||||
- build-windows:
|
||||
name: "build-windows-vs2017"
|
||||
vs_year: "2017"
|
||||
cmake_generator: "Visual Studio 15 Win64"
|
||||
- build-cmake-mingw
|
||||
jobs-java:
|
||||
jobs:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
$VS_DOWNLOAD_LINK = "https://go.microsoft.com/fwlink/?LinkId=691126"
|
||||
$COLLECT_DOWNLOAD_LINK = "https://aka.ms/vscollect.exe"
|
||||
curl.exe --retry 3 -kL $VS_DOWNLOAD_LINK --output vs_installer.exe
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
echo "Download of the VS 2015 installer failed"
|
||||
exit 1
|
||||
}
|
||||
$VS_INSTALL_ARGS = @("/Quiet", "/NoRestart")
|
||||
$process = Start-Process "${PWD}\vs_installer.exe" -ArgumentList $VS_INSTALL_ARGS -NoNewWindow -Wait -PassThru
|
||||
Remove-Item -Path vs_installer.exe -Force
|
||||
$exitCode = $process.ExitCode
|
||||
if (($exitCode -ne 0) -and ($exitCode -ne 3010)) {
|
||||
echo "VS 2015 installer exited with code $exitCode, which should be one of [0, 3010]."
|
||||
curl.exe --retry 3 -kL $COLLECT_DOWNLOAD_LINK --output Collect.exe
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
echo "Download of the VS Collect tool failed."
|
||||
exit 1
|
||||
}
|
||||
Start-Process "${PWD}\Collect.exe" -NoNewWindow -Wait -PassThru
|
||||
New-Item -Path "C:\w\build-results" -ItemType "directory" -Force
|
||||
Copy-Item -Path "C:\Users\circleci\AppData\Local\Temp\vslogs.zip" -Destination "C:\w\build-results\"
|
||||
exit 1
|
||||
}
|
||||
echo "VS 2015 installed."
|
||||
@@ -0,0 +1,35 @@
|
||||
$VS_DOWNLOAD_LINK = "https://aka.ms/vs/15/release/vs_buildtools.exe"
|
||||
$COLLECT_DOWNLOAD_LINK = "https://aka.ms/vscollect.exe"
|
||||
$VS_INSTALL_ARGS = @("--nocache","--quiet","--wait", "--add Microsoft.VisualStudio.Workload.VCTools",
|
||||
"--add Microsoft.VisualStudio.Component.VC.Tools.14.13",
|
||||
"--add Microsoft.Component.MSBuild",
|
||||
"--add Microsoft.VisualStudio.Component.Roslyn.Compiler",
|
||||
"--add Microsoft.VisualStudio.Component.TextTemplating",
|
||||
"--add Microsoft.VisualStudio.Component.VC.CoreIde",
|
||||
"--add Microsoft.VisualStudio.Component.VC.Redist.14.Latest",
|
||||
"--add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Core",
|
||||
"--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"--add Microsoft.VisualStudio.ComponentGroup.NativeDesktop.Win81")
|
||||
|
||||
curl.exe --retry 3 -kL $VS_DOWNLOAD_LINK --output vs_installer.exe
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
echo "Download of the VS 2017 installer failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$process = Start-Process "${PWD}\vs_installer.exe" -ArgumentList $VS_INSTALL_ARGS -NoNewWindow -Wait -PassThru
|
||||
Remove-Item -Path vs_installer.exe -Force
|
||||
$exitCode = $process.ExitCode
|
||||
if (($exitCode -ne 0) -and ($exitCode -ne 3010)) {
|
||||
echo "VS 2017 installer exited with code $exitCode, which should be one of [0, 3010]."
|
||||
curl.exe --retry 3 -kL $COLLECT_DOWNLOAD_LINK --output Collect.exe
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
echo "Download of the VS Collect tool failed."
|
||||
exit 1
|
||||
}
|
||||
Start-Process "${PWD}\Collect.exe" -NoNewWindow -Wait -PassThru
|
||||
New-Item -Path "C:\w\build-results" -ItemType "directory" -Force
|
||||
Copy-Item -Path "C:\Users\circleci\AppData\Local\Temp\vslogs.zip" -Destination "C:\w\build-results\"
|
||||
exit 1
|
||||
}
|
||||
echo "VS 2017 installed."
|
||||
@@ -33,7 +33,9 @@ jobs:
|
||||
run: pip install argparse
|
||||
|
||||
- name: Download clang-format-diff.py
|
||||
run: wget https://raw.githubusercontent.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py
|
||||
uses: wei/wget@v1
|
||||
with:
|
||||
args: https://raw.githubusercontent.com/llvm/llvm-project/release/12.x/clang/tools/clang-format/clang-format-diff.py
|
||||
|
||||
- name: Check format
|
||||
run: VERBOSE_CHECK=1 make check-format
|
||||
|
||||
@@ -95,5 +95,3 @@ fuzz/crash-*
|
||||
|
||||
cmake-build-*
|
||||
third-party/folly/
|
||||
.cache
|
||||
*.sublime-*
|
||||
+62
-52
@@ -245,40 +245,39 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "s390x")
|
||||
endif(HAS_S390X_MARCH_NATIVE)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "s390x")
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "loongarch64")
|
||||
CHECK_C_COMPILER_FLAG("-march=loongarch64" HAS_LOONGARCH64)
|
||||
if(HAS_LOONGARCH64)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=loongarch64 -mtune=loongarch64")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=loongarch64 -mtune=loongarch64")
|
||||
endif(HAS_LOONGARCH64)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "loongarch64")
|
||||
|
||||
set(PORTABLE 0 CACHE STRING "Minimum CPU arch to support, or 0 = current CPU, 1 = baseline CPU")
|
||||
if(PORTABLE STREQUAL 1)
|
||||
# Usually nothing to do; compiler default is typically the most general
|
||||
if(NOT MSVC)
|
||||
option(PORTABLE "build a portable binary" OFF)
|
||||
option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
|
||||
option(FORCE_AVX "force building with AVX, even when PORTABLE=ON" OFF)
|
||||
option(FORCE_AVX2 "force building with AVX2, even when PORTABLE=ON" OFF)
|
||||
if(PORTABLE)
|
||||
# MSVC does not need a separate compiler flag to enable SSE4.2; if nmmintrin.h
|
||||
# is available, it is available by default.
|
||||
if(FORCE_SSE42 AND NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -mpclmul")
|
||||
endif()
|
||||
if(MSVC)
|
||||
if(FORCE_AVX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX")
|
||||
endif()
|
||||
# MSVC automatically enables BMI / lzcnt with AVX2.
|
||||
if(FORCE_AVX2)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
endif()
|
||||
else()
|
||||
if(FORCE_AVX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx")
|
||||
endif()
|
||||
if(FORCE_AVX2)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2 -mbmi -mlzcnt")
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^loongarch64")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=loongarch64")
|
||||
endif()
|
||||
endif()
|
||||
elseif(PORTABLE MATCHES [^0]+)
|
||||
# Name of a CPU arch spec or feature set to require
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:${PORTABLE}")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${PORTABLE}")
|
||||
endif()
|
||||
else()
|
||||
if(MSVC)
|
||||
# NOTE: No auto-detection of current CPU, but instead assume some useful
|
||||
# level of optimization is supported
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
else()
|
||||
# Require instruction set from current CPU (with some legacy or opt-out
|
||||
# exceptions)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x" AND NOT HAS_S390X_MARCH_NATIVE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
|
||||
elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64" AND NOT HAS_ARMV8_CRC)
|
||||
@@ -293,6 +292,27 @@ if(NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
|
||||
if (NOT PORTABLE OR FORCE_SSE42)
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <cstdint>
|
||||
#include <nmmintrin.h>
|
||||
#include <wmmintrin.h>
|
||||
int main() {
|
||||
volatile uint32_t x = _mm_crc32_u32(0, 0);
|
||||
const auto a = _mm_set_epi64x(0, 0);
|
||||
const auto b = _mm_set_epi64x(0, 0);
|
||||
const auto c = _mm_clmulepi64_si128(a, b, 0x00);
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
}
|
||||
" HAVE_SSE42)
|
||||
if(HAVE_SSE42)
|
||||
add_definitions(-DHAVE_SSE42)
|
||||
add_definitions(-DHAVE_PCLMUL)
|
||||
elseif(FORCE_SSE42)
|
||||
message(FATAL_ERROR "FORCE_SSE42=ON but unable to compile with SSE4.2 enabled")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Check if -latomic is required or not
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++17")
|
||||
@@ -465,6 +485,12 @@ if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp")
|
||||
endif()
|
||||
|
||||
option(ROCKSDB_LITE "Build RocksDBLite version" OFF)
|
||||
if(ROCKSDB_LITE)
|
||||
add_definitions(-DROCKSDB_LITE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -Os")
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Cygwin")
|
||||
add_definitions(-fno-builtin-memcmp -DCYGWIN)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
@@ -547,7 +573,7 @@ if(HAVE_SCHED_GETCPU)
|
||||
add_definitions(-DROCKSDB_SCHED_GETCPU_PRESENT)
|
||||
endif()
|
||||
|
||||
check_cxx_symbol_exists(getauxval "sys/auxv.h" HAVE_AUXV_GETAUXVAL)
|
||||
check_cxx_symbol_exists(getauxval auvx.h HAVE_AUXV_GETAUXVAL)
|
||||
if(HAVE_AUXV_GETAUXVAL)
|
||||
add_definitions(-DROCKSDB_AUXV_GETAUXVAL_PRESENT)
|
||||
endif()
|
||||
@@ -623,14 +649,12 @@ set(SOURCES
|
||||
cache/cache.cc
|
||||
cache/cache_entry_roles.cc
|
||||
cache/cache_key.cc
|
||||
cache/cache_helpers.cc
|
||||
cache/cache_reservation_manager.cc
|
||||
cache/charged_cache.cc
|
||||
cache/clock_cache.cc
|
||||
cache/compressed_secondary_cache.cc
|
||||
cache/fast_lru_cache.cc
|
||||
cache/lru_cache.cc
|
||||
cache/secondary_cache.cc
|
||||
cache/secondary_cache_adapter.cc
|
||||
cache/sharded_cache.cc
|
||||
db/arena_wrapped_db_iter.cc
|
||||
db/blob/blob_contents.cc
|
||||
@@ -717,7 +741,6 @@ set(SOURCES
|
||||
db/write_batch.cc
|
||||
db/write_batch_base.cc
|
||||
db/write_controller.cc
|
||||
db/write_stall_stats.cc
|
||||
db/write_thread.cc
|
||||
env/composite_env.cc
|
||||
env/env.cc
|
||||
@@ -773,7 +796,6 @@ set(SOURCES
|
||||
options/options.cc
|
||||
options/options_helper.cc
|
||||
options/options_parser.cc
|
||||
port/mmap.cc
|
||||
port/stack_trace.cc
|
||||
table/adaptive/adaptive_table_factory.cc
|
||||
table/block_based/binary_search_index_reader.cc
|
||||
@@ -783,7 +805,6 @@ set(SOURCES
|
||||
table/block_based/block_based_table_iterator.cc
|
||||
table/block_based/block_based_table_reader.cc
|
||||
table/block_based/block_builder.cc
|
||||
table/block_based/block_cache.cc
|
||||
table/block_based/block_prefetcher.cc
|
||||
table/block_based/block_prefix_index.cc
|
||||
table/block_based/data_block_hash_index.cc
|
||||
@@ -809,7 +830,6 @@ set(SOURCES
|
||||
table/get_context.cc
|
||||
table/iterator.cc
|
||||
table/merging_iterator.cc
|
||||
table/compaction_merging_iterator.cc
|
||||
table/meta_blocks.cc
|
||||
table/persistent_cache_helper.cc
|
||||
table/plain/plain_table_bloom.cc
|
||||
@@ -851,7 +871,6 @@ set(SOURCES
|
||||
util/compression_context_cache.cc
|
||||
util/concurrent_task_limiter_impl.cc
|
||||
util/crc32c.cc
|
||||
util/data_structure.cc
|
||||
util/dynamic_bloom.cc
|
||||
util/hash.cc
|
||||
util/murmurhash.cc
|
||||
@@ -865,8 +884,6 @@ set(SOURCES
|
||||
util/string_util.cc
|
||||
util/thread_local.cc
|
||||
util/threadpool_imp.cc
|
||||
util/udt_util.cc
|
||||
util/write_batch_util.cc
|
||||
util/xxhash.cc
|
||||
utilities/agg_merge/agg_merge.cc
|
||||
utilities/backup/backup_engine.cc
|
||||
@@ -961,12 +978,6 @@ if ( ROCKSDB_PLUGINS )
|
||||
plugin/${plugin}/${src}
|
||||
PROPERTIES COMPILE_FLAGS "${${plugin}_COMPILE_FLAGS}")
|
||||
endforeach()
|
||||
foreach (test ${${plugin}_TESTS})
|
||||
list(APPEND PLUGIN_TESTS plugin/${plugin}/${test})
|
||||
set_source_files_properties(
|
||||
plugin/${plugin}/${test}
|
||||
PROPERTIES COMPILE_FLAGS "${${plugin}_COMPILE_FLAGS}")
|
||||
endforeach()
|
||||
foreach (path ${${plugin}_INCLUDE_PATHS})
|
||||
include_directories(${path})
|
||||
endforeach()
|
||||
@@ -981,6 +992,12 @@ if ( ROCKSDB_PLUGINS )
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(HAVE_SSE42 AND NOT MSVC)
|
||||
set_source_files_properties(
|
||||
util/crc32c.cc
|
||||
PROPERTIES COMPILE_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
|
||||
list(APPEND SOURCES
|
||||
util/crc32c_ppc.c
|
||||
@@ -1246,7 +1263,6 @@ if(WITH_TESTS OR WITH_BENCHMARK_TOOLS)
|
||||
add_subdirectory(third-party/gtest-1.8.1/fused-src/gtest)
|
||||
add_library(testharness STATIC
|
||||
test_util/mock_time_env.cc
|
||||
test_util/secondary_cache_test_util.cc
|
||||
test_util/testharness.cc)
|
||||
target_link_libraries(testharness gtest)
|
||||
endif()
|
||||
@@ -1292,7 +1308,6 @@ if(WITH_TESTS)
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_compaction_filter_test.cc
|
||||
db/db_compaction_test.cc
|
||||
db/db_clip_test.cc
|
||||
db/db_dynamic_level_test.cc
|
||||
db/db_encryption_test.cc
|
||||
db/db_flush_test.cc
|
||||
@@ -1423,7 +1438,6 @@ if(WITH_TESTS)
|
||||
util/timer_test.cc
|
||||
util/thread_list_test.cc
|
||||
util/thread_local_test.cc
|
||||
util/udt_util_test.cc
|
||||
util/work_queue_test.cc
|
||||
utilities/agg_merge/agg_merge_test.cc
|
||||
utilities/backup/backup_engine_test.cc
|
||||
@@ -1455,7 +1469,6 @@ if(WITH_TESTS)
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/util_merge_operators_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
${PLUGIN_TESTS}
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -1559,9 +1572,9 @@ endif()
|
||||
|
||||
option(WITH_TRACE_TOOLS "build with trace tools" ON)
|
||||
if(WITH_TRACE_TOOLS)
|
||||
add_executable(block_cache_trace_analyzer${ARTIFACT_SUFFIX}
|
||||
add_executable(block_cache_trace_analyzer_tool${ARTIFACT_SUFFIX}
|
||||
tools/block_cache_analyzer/block_cache_trace_analyzer_tool.cc)
|
||||
target_link_libraries(block_cache_trace_analyzer${ARTIFACT_SUFFIX}
|
||||
target_link_libraries(block_cache_trace_analyzer_tool${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
|
||||
add_executable(trace_analyzer${ARTIFACT_SUFFIX}
|
||||
@@ -1592,6 +1605,3 @@ option(WITH_BENCHMARK "build benchmark tests" OFF)
|
||||
if(WITH_BENCHMARK)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/microbench/)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>)
|
||||
+10
-265
@@ -1,274 +1,20 @@
|
||||
# Rocksdb Change Log
|
||||
> NOTE: Entries for next release do not go here. Follow instructions in `unreleased_history/README.txt`
|
||||
## 7.8.3 (11/29/2022)
|
||||
* Revert an internal change in 7.8.0 associated with some memory usage churn.
|
||||
|
||||
## 8.6.7 (09/26/2023)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where compaction read under non direct IO still falls back to RocksDB internal prefetching after file system's prefetching returns non-OK status other than `Status::NotSupported()`
|
||||
|
||||
### Behavior Changes
|
||||
* For non direct IO, eliminate the file system prefetching attempt for compaction read when `Options::compaction_readahead_size` is 0
|
||||
|
||||
## 8.6.6 (09/25/2023)
|
||||
### Bug Fixes
|
||||
* Fix a bug with atomic_flush=true that can cause DB to stuck after a flush fails (#11872).
|
||||
* Fix a bug where RocksDB (with atomic_flush=false) can delete output SST files of pending flushes when a previous concurrent flush fails (#11865). This can result in DB entering read-only state with error message like `IO error: No such file or directory: While open a file for random read: /tmp/rocksdbtest-501/db_flush_test_87732_4230653031040984171/000013.sst`.
|
||||
* When the compressed secondary cache capacity is reduced to 0, it should be completely disabled. Before this fix, inserts and lookups would still go to the backing `LRUCache` before returning, thus incurring locking overhead. With this fix, inserts and lookups are no-ops and do not add any overhead.
|
||||
|
||||
## 8.6.5 (09/15/2023)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where `rocksdb.file.read.verify.file.checksums.micros` is not populated.
|
||||
|
||||
## 8.6.4 (09/13/2023)
|
||||
### Public API changes
|
||||
* Add a column family option `default_temperature` that is used for file reading accounting purpose, such as io statistics, for files that don't have an explicitly set temperature.
|
||||
|
||||
## 8.6.3 (09/12/2023)
|
||||
### Bug Fixes
|
||||
* Fix a bug where if there is an error reading from offset 0 of a file from L1+ and that the file is not the first file in the sorted run, data can be lost in compaction and read/scan can return incorrect results.
|
||||
* Fix a bug where iterator may return incorrect result for DeleteRange() users if there was an error reading from a file.
|
||||
|
||||
## 8.6.2 (09/11/2023)
|
||||
### Bug Fixes
|
||||
* Add a fix for async_io where during seek, when reading a block for seeking a target key in a file without any readahead, the iterator aligned the read on a page boundary and reading more than necessary. This increased the storage read bandwidth usage.
|
||||
|
||||
## 8.6.1 (08/30/2023)
|
||||
### Public API Changes
|
||||
* `Options::compaction_readahead_size` 's default value is changed from 0 to 2MB.
|
||||
|
||||
### Behavior Changes
|
||||
* Compaction read performance will regress when `Options::compaction_readahead_size` is explicitly set to 0
|
||||
|
||||
## 8.6.0 (08/18/2023)
|
||||
### New Features
|
||||
* Added enhanced data integrity checking on SST files with new format_version=6. Performance impact is very small or negligible. Previously if SST data was misplaced or re-arranged by the storage layer, it could pass block checksum with higher than 1 in 4 billion probability. With format_version=6, block checksums depend on what file they are in and location within the file. This way, misplaced SST data is no more likely to pass checksum verification than randomly corrupted data. Also in format_version=6, SST footers are checksum-protected.
|
||||
* Add a new feature to trim readahead_size during scans upto upper_bound when iterate_upper_bound is specified. It's enabled through ReadOptions.auto_readahead_size. Users must also specify ReadOptions.iterate_upper_bound.
|
||||
* RocksDB will compare the number of input keys to the number of keys processed after each compaction. Compaction will fail and report Corruption status if the verification fails. Option `compaction_verify_record_count` is introduced for this purpose and is enabled by default.
|
||||
* Add a CF option `bottommost_file_compaction_delay` to allow specifying the delay of bottommost level single-file compactions.
|
||||
* Add support to allow enabling / disabling user-defined timestamps feature for an existing column family in combination with the in-Memtable only feature.
|
||||
* Implement a new admission policy for the compressed secondary cache that admits blocks evicted from the primary cache with the hit bit set. This policy can be specified in TieredVolatileCacheOptions by setting the newly added adm_policy option.
|
||||
* Add a column family option `memtable_max_range_deletions` that limits the number of range deletions in a memtable. RocksDB will try to do an automatic flush after the limit is reached. (#11358)
|
||||
* Add PutEntity API in sst_file_writer
|
||||
* Add `timeout` in microsecond option to `WaitForCompactOptions` to allow timely termination of prolonged waiting in scenarios like recurring recoverable errors, such as out-of-space situations and continuous write streams that sustain ongoing flush and compactions
|
||||
* New statistics `rocksdb.file.read.{get|multiget|db.iterator|verify.checksum|verify.file.checksums}.micros` measure read time of block-based SST tables or blob files during db open, `Get()`, `MultiGet()`, using db iterator, `VerifyFileChecksums()` and `VerifyChecksum()`. They require stats level greater than `StatsLevel::kExceptDetailedTimers`.
|
||||
* Add close_db option to `WaitForCompactOptions` to call Close() after waiting is done.
|
||||
* Add a new compression option `CompressionOptions::checksum` for enabling ZSTD's checksum feature to detect corruption during decompression.
|
||||
|
||||
### Public API Changes
|
||||
* Mark `Options::access_hint_on_compaction_start` related APIs as deprecated. See #11631 for alternative behavior.
|
||||
|
||||
### Behavior Changes
|
||||
* Statistics `rocksdb.sst.read.micros` now includes time spent on multi read and async read into the file
|
||||
* For Universal Compaction users, periodic compaction (option `periodic_compaction_seconds`) will be set to 30 days by default if block based table is used.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in FileTTLBooster that can cause users with a large number of levels (more than 65) to see errors like "runtime error: shift exponent .. is too large.." (#11673).
|
||||
|
||||
## 8.5.0 (07/21/2023)
|
||||
### Public API Changes
|
||||
* Removed recently added APIs `GeneralCache` and `MakeSharedGeneralCache()` as our plan changed to stop exposing a general-purpose cache interface. The old forms of these APIs, `Cache` and `NewLRUCache()`, are still available, although general-purpose caching support will be dropped eventually.
|
||||
|
||||
### Behavior Changes
|
||||
* Option `periodic_compaction_seconds` no longer supports FIFO compaction: setting it has no effect on FIFO compactions. FIFO compaction users should only set option `ttl` instead.
|
||||
* Move prefetching responsibility to page cache for compaction read for non directIO use case
|
||||
|
||||
### Performance Improvements
|
||||
* In case of direct_io, if buffer passed by callee is already aligned, RandomAccessFileRead::Read will avoid realloacting a new buffer, reducing memcpy and use already passed aligned buffer.
|
||||
* Small efficiency improvement to HyperClockCache by reducing chance of compiler-generated heap allocations
|
||||
|
||||
### Bug Fixes
|
||||
* Fix use_after_free bug in async_io MultiReads when underlying FS enabled kFSBuffer. kFSBuffer is when underlying FS pass their own buffer instead of using RocksDB scratch in FSReadRequest. Right now it's an experimental feature.
|
||||
|
||||
## 8.4.0 (06/26/2023)
|
||||
### New Features
|
||||
* Add FSReadRequest::fs_scratch which is a data buffer allocated and provided by underlying FileSystem to RocksDB during reads, when FS wants to provide its own buffer with data instead of using RocksDB provided FSReadRequest::scratch. This can help in cpu optimization by avoiding copy from file system's buffer to RocksDB buffer. More details on how to use/enable it in file_system.h. Right now its supported only for MultiReads(async + sync) with non direct io.
|
||||
* Start logging non-zero user-defined timestamp sizes in WAL to signal user key format in subsequent records and use it during recovery. This change will break recovery from WAL files written by early versions that contain user-defined timestamps. The workaround is to ensure there are no WAL files to recover (i.e. by flushing before close) before upgrade.
|
||||
* Added new property "rocksdb.obsolete-sst-files-size-property" that reports the size of SST files that have become obsolete but have not yet been deleted or scheduled for deletion
|
||||
* Start to record the value of the flag `AdvancedColumnFamilyOptions.persist_user_defined_timestamps` in the Manifest and table properties for a SST file when it is created. And use the recorded flag when creating a table reader for the SST file. This flag is only explicitly record if it's false.
|
||||
* Add a new option OptimisticTransactionDBOptions::shared_lock_buckets that enables sharing mutexes for validating transactions between DB instances, for better balancing memory efficiency and validation contention across DB instances. Different column families and DBs also now use different hash seeds in this validation, so that the same set of key names will not contend across DBs or column families.
|
||||
* Add a new ticker `rocksdb.files.marked.trash.deleted` to track the number of trash files deleted by background thread from the trash queue.
|
||||
* Add an API NewTieredVolatileCache() in include/rocksdb/cache.h to allocate an instance of a block cache with a primary block cache tier and a compressed secondary cache tier. A cache of this type distributes memory reservations against the block cache, such as WriteBufferManager, table reader memory etc., proportionally across both the primary and compressed secondary cache.
|
||||
* Add `WaitForCompact()` to wait for all flush and compactions jobs to finish. Jobs to wait include the unscheduled (queued, but not scheduled yet).
|
||||
* Add `WriteBatch::Release()` that releases the batch's serialized data to the caller.
|
||||
|
||||
### Public API Changes
|
||||
* Add C API `rocksdb_options_add_compact_on_deletion_collector_factory_del_ratio`.
|
||||
* change the FileSystem::use_async_io() API to SupportedOps API in order to extend it to various operations supported by underlying FileSystem. Right now it contains FSSupportedOps::kAsyncIO and FSSupportedOps::kFSBuffer. More details about FSSupportedOps in filesystem.h
|
||||
* Add new tickers: `rocksdb.error.handler.bg.error.count`, `rocksdb.error.handler.bg.io.error.count`, `rocksdb.error.handler.bg.retryable.io.error.count` to replace the misspelled ones: `rocksdb.error.handler.bg.errro.count`, `rocksdb.error.handler.bg.io.errro.count`, `rocksdb.error.handler.bg.retryable.io.errro.count` ('error' instead of 'errro'). Users should switch to use the new tickers before 9.0 release as the misspelled old tickers will be completely removed then.
|
||||
* Overload the API CreateColumnFamilyWithImport() to support creating ColumnFamily by importing multiple ColumnFamilies It requires that CFs should not overlap in user key range.
|
||||
|
||||
### Behavior Changes
|
||||
* Change the default value for option `level_compaction_dynamic_level_bytes` to true. This affects users who use leveled compaction and do not set this option explicitly. These users may see additional background compactions following DB open. These compactions help to shape the LSM according to `level_compaction_dynamic_level_bytes` such that the size of each level Ln is approximately size of Ln-1 * `max_bytes_for_level_multiplier`. Turning on this option has other benefits too: see more detail in wiki: https://github.com/facebook/rocksdb/wiki/Leveled-Compaction#option-level_compaction_dynamic_level_bytes-and-levels-target-size and in option comment in advanced_options.h (#11525).
|
||||
* For Leveled Compaction users, `CompactRange()` will now always try to compact to the last non-empty level. (#11468)
|
||||
For Leveled Compaction users, `CompactRange()` with `bottommost_level_compaction = BottommostLevelCompaction::kIfHaveCompactionFilter` will behave similar to `kForceOptimized` in that it will skip files created during this manual compaction when compacting files in the bottommost level. (#11468)
|
||||
* RocksDB will try to drop range tombstones during non-bottommost compaction when it is safe to do so. (#11459)
|
||||
* When a DB is openend with `allow_ingest_behind=true` (currently only Universal compaction is supported), files in the last level, i.e. the ingested files, will not be included in any compaction. (#11489)
|
||||
* Statistics `rocksdb.sst.read.micros` scope is expanded to all SST reads except for file ingestion and column family import (some compaction reads were previously excluded).
|
||||
|
||||
### Bug Fixes
|
||||
* Reduced cases of illegally using Env::Default() during static destruction by never destroying the internal PosixEnv itself (except for builds checking for memory leaks). (#11538)
|
||||
* Fix extra prefetching during seek in async_io when BlockBasedTableOptions.num_file_reads_for_auto_readahead is 1 leading to extra reads than required.
|
||||
* Fix a bug where compactions that are qualified to be run as 2 subcompactions were only run as one subcompaction.
|
||||
* Fix a use-after-move bug in block.cc.
|
||||
|
||||
## 8.3.0 (05/19/2023)
|
||||
### New Features
|
||||
* Introduced a new option `block_protection_bytes_per_key`, which can be used to enable per key-value integrity protection for in-memory blocks in block cache (#11287).
|
||||
* Added `JemallocAllocatorOptions::num_arenas`. Setting `num_arenas > 1` may mitigate mutex contention in the allocator, particularly in scenarios where block allocations commonly bypass jemalloc tcache.
|
||||
* Improve the operational safety of publishing a DB or SST files to many hosts by using different block cache hash seeds on different hosts. The exact behavior is controlled by new option `ShardedCacheOptions::hash_seed`, which also documents the solved problem in more detail.
|
||||
* Introduced a new option `CompactionOptionsFIFO::file_temperature_age_thresholds` that allows FIFO compaction to compact files to different temperatures based on key age (#11428).
|
||||
* Added a new ticker stat to count how many times RocksDB detected a corruption while verifying a block checksum: `BLOCK_CHECKSUM_MISMATCH_COUNT`.
|
||||
* New statistics `rocksdb.file.read.db.open.micros` that measures read time of block-based SST tables or blob files during db open.
|
||||
* New statistics tickers for various iterator seek behaviors and relevant filtering, as \*`_LEVEL_SEEK_`\*. (#11460)
|
||||
|
||||
### Public API Changes
|
||||
* EXPERIMENTAL: Add new API `DB::ClipColumnFamily` to clip the key in CF to a certain range. It will physically deletes all keys outside the range including tombstones.
|
||||
* Add `MakeSharedCache()` construction functions to various cache Options objects, and deprecated the `NewWhateverCache()` functions with long parameter lists.
|
||||
* Changed the meaning of various Bloom filter stats (prefix vs. whole key), with iterator-related filtering only being tracked in the new \*`_LEVEL_SEEK_`\*. stats. (#11460)
|
||||
|
||||
### Behavior changes
|
||||
* For x86, CPU features are no longer detected at runtime nor in build scripts, but in source code using common preprocessor defines. This will likely unlock some small performance improvements on some newer hardware, but could hurt performance of the kCRC32c checksum, which is no longer the default, on some "portable" builds. See PR #11419 for details.
|
||||
|
||||
### Bug Fixes
|
||||
* Delete an empty WAL file on DB open if the log number is less than the min log number to keep
|
||||
* Delete temp OPTIONS file on DB open if there is a failure to write it out or rename it
|
||||
|
||||
### Performance Improvements
|
||||
* Improved the I/O efficiency of prefetching SST metadata by recording more information in the DB manifest. Opening files written with previous versions will still rely on heuristics for how much to prefetch (#11406).
|
||||
|
||||
## 8.2.0 (04/24/2023)
|
||||
### Public API Changes
|
||||
* `SstFileWriter::DeleteRange()` now returns `Status::InvalidArgument` if the range's end key comes before its start key according to the user comparator. Previously the behavior was undefined.
|
||||
* Add `multi_get_for_update` to C API.
|
||||
* Remove unnecessary constructor for CompressionOptions.
|
||||
|
||||
### Behavior changes
|
||||
* Changed default block cache size from an 8MB to 32MB LRUCache, which increases the default number of cache shards from 16 to 64. This change is intended to minimize cache mutex contention under stress conditions. See https://github.com/facebook/rocksdb/wiki/Block-Cache for more information.
|
||||
* For level compaction with `level_compaction_dynamic_level_bytes=true`, RocksDB now trivially moves levels down to fill LSM starting from bottommost level during DB open. See more in comments for option `level_compaction_dynamic_level_bytes` (#11321).
|
||||
* User-provided `ReadOptions` take effect for more reads of non-`CacheEntryRole::kDataBlock` blocks.
|
||||
* For level compaction with `level_compaction_dynamic_level_bytes=true`, RocksDB now drains unnecessary levels through background compaction automatically (#11340). This together with #11321 makes it automatic to migrate other compaction settings to level compaction with `level_compaction_dynamic_level_bytes=true`. In addition, a live DB that becomes smaller will now have unnecessary levels drained which can help to reduce read and space amp.
|
||||
* If `CompactRange()` is called with `CompactRangeOptions::bottommost_level_compaction=kForce*` to compact from L0 to L1, RocksDB now will try to do trivial move from L0 to L1 and then do an intra L1 compaction, instead of a L0 to L1 compaction with trivial move disabled (#11375)).
|
||||
|
||||
### Bug Fixes
|
||||
* In the DB::VerifyFileChecksums API, ensure that file system reads of SST files are equal to the readahead_size in ReadOptions, if specified. Previously, each read was 2x the readahead_size.
|
||||
* In block cache tracing, fixed some cases of bad hit/miss information (and more) with MultiGet.
|
||||
|
||||
### New Features
|
||||
* Add experimental `PerfContext` counters `iter_{next|prev|seek}_count` for db iterator, each counting the times of corresponding API being called.
|
||||
* Allow runtime changes to whether `WriteBufferManager` allows stall or not by calling `SetAllowStall()`
|
||||
* Added statistics tickers BYTES_COMPRESSED_FROM, BYTES_COMPRESSED_TO, BYTES_COMPRESSION_BYPASSED, BYTES_COMPRESSION_REJECTED, NUMBER_BLOCK_COMPRESSION_BYPASSED, and NUMBER_BLOCK_COMPRESSION_REJECTED. Disabled/deprecated histograms BYTES_COMPRESSED and BYTES_DECOMPRESSED, and ticker NUMBER_BLOCK_NOT_COMPRESSED. The new tickers offer more inight into compression ratios, rejected vs. disabled compression, etc. (#11388)
|
||||
* New statistics `rocksdb.file.read.{flush|compaction}.micros` that measure read time of block-based SST tables or blob files during flush or compaction.
|
||||
|
||||
## 8.1.0 (03/18/2023)
|
||||
### Behavior changes
|
||||
* Compaction output file cutting logic now considers range tombstone start keys. For example, SST partitioner now may receive ParitionRequest for range tombstone start keys.
|
||||
* If the async_io ReadOption is specified for MultiGet or NewIterator on a platform that doesn't support IO uring, the option is ignored and synchronous IO is used.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed an issue for backward iteration when user defined timestamp is enabled in combination with BlobDB.
|
||||
* Fixed a couple of cases where a Merge operand encountered during iteration wasn't reflected in the `internal_merge_count` PerfContext counter.
|
||||
* Fixed a bug in CreateColumnFamilyWithImport()/ExportColumnFamily() which did not support range tombstones (#11252).
|
||||
* Fixed a bug where an excluded column family from an atomic flush contains unflushed data that should've been included in this atomic flush (i.e, data of seqno less than the max seqno of this atomic flush), leading to potential data loss in this excluded column family when `WriteOptions::disableWAL == true` (#11148).
|
||||
|
||||
### New Features
|
||||
* Add statistics rocksdb.secondary.cache.filter.hits, rocksdb.secondary.cache.index.hits, and rocksdb.secondary.cache.filter.hits
|
||||
* Added a new PerfContext counter `internal_merge_point_lookup_count` which tracks the number of Merge operands applied while serving point lookup queries.
|
||||
* Add new statistics rocksdb.table.open.prefetch.tail.read.bytes, rocksdb.table.open.prefetch.tail.{miss|hit}
|
||||
* Add support for SecondaryCache with HyperClockCache (`HyperClockCacheOptions` inherits `secondary_cache` option from `ShardedCacheOptions`)
|
||||
* Add new db properties `rocksdb.cf-write-stall-stats`, `rocksdb.db-write-stall-stats`and APIs to examine them in a structured way. In particular, users of `GetMapProperty()` with property `kCFWriteStallStats`/`kDBWriteStallStats` can now use the functions in `WriteStallStatsMapKeys` to find stats in the map.
|
||||
|
||||
### Public API Changes
|
||||
* Changed various functions and features in `Cache` that are mostly relevant to custom implementations or wrappers. Especially, asychronous lookup functionality is moved from `Lookup()` to a new `StartAsyncLookup()` function.
|
||||
|
||||
## 8.0.0 (02/19/2023)
|
||||
### Behavior changes
|
||||
* `ReadOptions::verify_checksums=false` disables checksum verification for more reads of non-`CacheEntryRole::kDataBlock` blocks.
|
||||
* In case of scan with async_io enabled, if posix doesn't support IOUring, Status::NotSupported error will be returned to the users. Initially that error was swallowed and reads were switched to synchronous reads.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a data race on `ColumnFamilyData::flush_reason` caused by concurrent flushes.
|
||||
* Fixed an issue in `Get` and `MultiGet` when user-defined timestamps is enabled in combination with BlobDB.
|
||||
* Fixed some atypical behaviors for `LockWAL()` such as allowing concurrent/recursive use and not expecting `UnlockWAL()` after non-OK result. See API comments.
|
||||
* Fixed a feature interaction bug where for blobs `GetEntity` would expose the blob reference instead of the blob value.
|
||||
* Fixed `DisableManualCompaction()` and `CompactRangeOptions::canceled` to cancel compactions even when they are waiting on conflicting compactions to finish
|
||||
* Fixed a bug in which a successful `GetMergeOperands()` could transiently return `Status::MergeInProgress()`
|
||||
* Return the correct error (Status::NotSupported()) to MultiGet caller when ReadOptions::async_io flag is true and IO uring is not enabled. Previously, Status::Corruption() was being returned when the actual failure was lack of async IO support.
|
||||
* Fixed a bug in DB open/recovery from a compressed WAL that was caused due to incorrect handling of certain record fragments with the same offset within a WAL block.
|
||||
|
||||
### Feature Removal
|
||||
* Remove RocksDB Lite.
|
||||
* The feature block_cache_compressed is removed. Statistics related to it are removed too.
|
||||
* Remove deprecated Env::LoadEnv(). Use Env::CreateFromString() instead.
|
||||
* Remove deprecated FileSystem::Load(). Use FileSystem::CreateFromString() instead.
|
||||
* Removed the deprecated version of these utility functions and the corresponding Java bindings: `LoadOptionsFromFile`, `LoadLatestOptions`, `CheckOptionsCompatibility`.
|
||||
* Remove the FactoryFunc from the LoadObject method from the Customizable helper methods.
|
||||
|
||||
### Public API Changes
|
||||
* Moved rarely-needed Cache class definition to new advanced_cache.h, and added a CacheWrapper class to advanced_cache.h. Minor changes to SimCache API definitions.
|
||||
* Completely removed the following deprecated/obsolete statistics: the tickers `BLOCK_CACHE_INDEX_BYTES_EVICT`, `BLOCK_CACHE_FILTER_BYTES_EVICT`, `BLOOM_FILTER_MICROS`, `NO_FILE_CLOSES`, `STALL_L0_SLOWDOWN_MICROS`, `STALL_MEMTABLE_COMPACTION_MICROS`, `STALL_L0_NUM_FILES_MICROS`, `RATE_LIMIT_DELAY_MILLIS`, `NO_ITERATORS`, `NUMBER_FILTERED_DELETES`, `WRITE_TIMEDOUT`, `BLOB_DB_GC_NUM_KEYS_OVERWRITTEN`, `BLOB_DB_GC_NUM_KEYS_EXPIRED`, `BLOB_DB_GC_BYTES_OVERWRITTEN`, `BLOB_DB_GC_BYTES_EXPIRED`, `BLOCK_CACHE_COMPRESSION_DICT_BYTES_EVICT` as well as the histograms `STALL_L0_SLOWDOWN_COUNT`, `STALL_MEMTABLE_COMPACTION_COUNT`, `STALL_L0_NUM_FILES_COUNT`, `HARD_RATE_LIMIT_DELAY_COUNT`, `SOFT_RATE_LIMIT_DELAY_COUNT`, `BLOB_DB_GC_MICROS`, and `NUM_DATA_BLOCKS_READ_PER_LEVEL`. Note that as a result, the C++ enum values of the still supported statistics have changed. Developers are advised to not rely on the actual numeric values.
|
||||
* Deprecated IngestExternalFileOptions::write_global_seqno and change default to false. This option only needs to be set to true to generate a DB compatible with RocksDB versions before 5.16.0.
|
||||
* Remove deprecated APIs `GetColumnFamilyOptionsFrom{Map|String}(const ColumnFamilyOptions&, ..)`, `GetDBOptionsFrom{Map|String}(const DBOptions&, ..)`, `GetBlockBasedTableOptionsFrom{Map|String}(const BlockBasedTableOptions& table_options, ..)` and ` GetPlainTableOptionsFrom{Map|String}(const PlainTableOptions& table_options,..)`.
|
||||
* Added a subcode of `Status::Corruption`, `Status::SubCode::kMergeOperatorFailed`, for users to identify corruption failures originating in the merge operator, as opposed to RocksDB's internally identified data corruptions
|
||||
|
||||
### Build Changes
|
||||
* The `make` build now builds a shared library by default instead of a static library. Use `LIB_MODE=static` to override.
|
||||
|
||||
### New Features
|
||||
* Compaction filters are now supported for wide-column entities by means of the `FilterV3` API. See the comment of the API for more details.
|
||||
* Added `do_not_compress_roles` to `CompressedSecondaryCacheOptions` to disable compression on certain kinds of block. Filter blocks are now not compressed by CompressedSecondaryCache by default.
|
||||
* Added a new `MultiGetEntity` API that enables batched wide-column point lookups. See the API comments for more details.
|
||||
|
||||
## 7.10.0 (01/23/2023)
|
||||
## 7.8.2 (11/27/2022)
|
||||
### Behavior changes
|
||||
* Make best-efforts recovery verify SST unique ID before Version construction (#10962)
|
||||
* Introduce `epoch_number` and sort L0 files by `epoch_number` instead of `largest_seqno`. `epoch_number` represents the order of a file being flushed or ingested/imported. Compaction output file will be assigned with the minimum `epoch_number` among input files'. For L0, larger `epoch_number` indicates newer L0 file.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a regression in iterator where range tombstones after `iterate_upper_bound` is processed.
|
||||
* Fixed a memory leak in MultiGet with async_io read option, caused by IO errors during table file open
|
||||
* Fixed a bug that multi-level FIFO compaction deletes one file in non-L0 even when `CompactionOptionsFIFO::max_table_files_size` is no exceeded since #10348 or 7.8.0.
|
||||
* Fixed a bug caused by `DB::SyncWAL()` affecting `track_and_verify_wals_in_manifest`. Without the fix, application may see "open error: Corruption: Missing WAL with log number" while trying to open the db. The corruption is a false alarm but prevents DB open (#10892).
|
||||
* Fixed a BackupEngine bug in which RestoreDBFromLatestBackup would fail if the latest backup was deleted and there is another valid backup available.
|
||||
* Fix L0 file misorder corruption caused by ingesting files of overlapping seqnos with memtable entries' through introducing `epoch_number`. Before the fix, `force_consistency_checks=true` may catch the corruption before it's exposed to readers, in which case writes returning `Status::Corruption` would be expected. Also replace the previous incomplete fix (#5958) to the same corruption with this new and more complete fix.
|
||||
* Fixed a bug in LockWAL() leading to re-locking mutex (#11020).
|
||||
* Fixed a heap use after free bug in async scan prefetching when the scan thread and another thread try to read and load the same seek block into cache.
|
||||
* Fixed a heap use after free in async scan prefetching if dictionary compression is enabled, in which case sync read of the compression dictionary gets mixed with async prefetching
|
||||
* Fixed a data race bug of `CompactRange()` under `change_level=true` acts on overlapping range with an ongoing file ingestion for level compaction. This will either result in overlapping file ranges corruption at a certain level caught by `force_consistency_checks=true` or protentially two same keys both with seqno 0 in two different levels (i.e, new data ends up in lower/older level). The latter will be caught by assertion in debug build but go silently and result in read returning wrong result in release build. This fix is general so it also replaced previous fixes to a similar problem for `CompactFiles()` (#4665), general `CompactRange()` and auto compaction (commit 5c64fb6 and 87dfc1d).
|
||||
* Fixed a bug in compaction output cutting where small output files were produced due to TTL file cutting states were not being updated (#11075).
|
||||
|
||||
### New Features
|
||||
* When an SstPartitionerFactory is configured, CompactRange() now automatically selects for compaction any files overlapping a partition boundary that is in the compaction range, even if no actual entries are in the requested compaction range. With this feature, manual compaction can be used to (re-)establish SST partition points when SstPartitioner changes, without a full compaction.
|
||||
* Add BackupEngine feature to exclude files from backup that are known to be backed up elsewhere, using `CreateBackupOptions::exclude_files_callback`. To restore the DB, the excluded files must be provided in alternative backup directories using `RestoreOptions::alternate_dirs`.
|
||||
|
||||
### Public API Changes
|
||||
* Substantial changes have been made to the Cache class to support internal development goals. Direct use of Cache class members is discouraged and further breaking modifications are expected in the future. SecondaryCache has some related changes and implementations will need to be updated. (Unlike Cache, SecondaryCache is still intended to support user implementations, and disruptive changes will be avoided.) (#10975)
|
||||
* Add `MergeOperationOutput::op_failure_scope` for merge operator users to control the blast radius of merge operator failures. Existing merge operator users do not need to make any change to preserve the old behavior
|
||||
|
||||
### Performance Improvements
|
||||
* Updated xxHash source code, which should improve kXXH3 checksum speed, at least on ARM (#11098).
|
||||
* Improved CPU efficiency of DB reads, from block cache access improvements (#10975).
|
||||
|
||||
## 7.9.0 (11/21/2022)
|
||||
### Performance Improvements
|
||||
* Fixed an iterator performance regression for delete range users when scanning through a consecutive sequence of range tombstones (#10877).
|
||||
|
||||
### Bug Fixes
|
||||
* Fix memory corruption error in scans if async_io is enabled. Memory corruption happened if there is IOError while reading the data leading to empty buffer and other buffer already in progress of async read goes again for reading.
|
||||
* Fix failed memtable flush retry bug that could cause wrongly ordered updates, which would surface to writers as `Status::Corruption` in case of `force_consistency_checks=true` (default). It affects use cases that enable both parallel flush (`max_background_flushes > 1` or `max_background_jobs >= 8`) and non-default memtable count (`max_write_buffer_number > 2`).
|
||||
* Fixed an issue where the `READ_NUM_MERGE_OPERANDS` ticker was not updated when the base key-value or tombstone was read from an SST file.
|
||||
* Fixed a memory safety bug when using a SecondaryCache with `block_cache_compressed`. `block_cache_compressed` no longer attempts to use SecondaryCache features.
|
||||
* Fixed a regression in scan for async_io. During seek, valid buffers were getting cleared causing a regression.
|
||||
* Tiered Storage: fixed excessive keys written to penultimate level in non-debug builds.
|
||||
|
||||
### New Features
|
||||
* Add basic support for user-defined timestamp to Merge (#10819).
|
||||
* Add stats for ReadAsync time spent and async read errors.
|
||||
* Basic support for the wide-column data model is now available. Wide-column entities can be stored using the `PutEntity` API, and retrieved using `GetEntity` and the new `columns` API of iterator. For compatibility, the classic APIs `Get` and `MultiGet`, as well as iterator's `value` API return the value of the anonymous default column of wide-column entities; also, `GetEntity` and iterator's `columns` return any plain key-values in the form of an entity which only has the anonymous default column. `Merge` (and `GetMergeOperands`) currently also apply to the default column; any other columns of entities are unaffected by `Merge` operations. Note that some features like compaction filters, transactions, user-defined timestamps, and the SST file writer do not yet support wide-column entities; also, there is currently no `MultiGet`-like API to retrieve multiple entities at once. We plan to gradually close the above gaps and also implement new features like column-level operations (e.g. updating or querying only certain columns of an entity).
|
||||
* Marked HyperClockCache as a production-ready alternative to LRUCache for the block cache. HyperClockCache greatly improves hot-path CPU efficiency under high parallel load or high contention, with some documented caveats and limitations. As much as 4.5x higher ops/sec vs. LRUCache has been seen in db_bench under high parallel load.
|
||||
* Add periodic diagnostics to info_log (LOG file) for HyperClockCache block cache if performance is degraded by bad `estimated_entry_charge` option.
|
||||
### Bug Fixes
|
||||
* Fixed a regression in scan for async_io. During seek, valid buffers were getting cleared causing a regression.
|
||||
* Fixed a performance regression in iterator where range tombstones after `iterate_upper_bound` is processed.
|
||||
|
||||
### Public API Changes
|
||||
* Marked `block_cache_compressed` as a deprecated feature. Use SecondaryCache instead.
|
||||
* Added a `SecondaryCache::InsertSaved()` API, with default implementation depending on `Insert()`. Some implementations might need to add a custom implementation of `InsertSaved()`. (Details in API comments.)
|
||||
## 7.8.1 (11/2/2022)
|
||||
### Bug Fixes
|
||||
* Fix memory corruption error in scans if async_io is enabled. Memory corruption happened if there is IOError while reading the data leading to empty buffer and other buffer already in progress of async read goes again for reading.
|
||||
|
||||
## 7.8.0 (10/22/2022)
|
||||
### New Features
|
||||
@@ -299,11 +45,10 @@ For Leveled Compaction users, `CompactRange()` with `bottommost_level_compaction
|
||||
* Try to align the compaction output file boundaries to the next level ones, which can reduce more than 10% compaction load for the default level compaction. The feature is enabled by default, to disable, set `AdvancedColumnFamilyOptions.level_compaction_dynamic_file_size` to false. As a side effect, it can create SSTs larger than the target_file_size (capped at 2x target_file_size) or smaller files.
|
||||
* Improve RoundRobin TTL compaction, which is going to be the same as normal RoundRobin compaction to move the compaction cursor.
|
||||
* Fix a small CPU regression caused by a change that UserComparatorWrapper was made Customizable, because Customizable itself has small CPU overhead for initialization.
|
||||
* Fixed an iterator performance regression for delete range users when scanning through a consecutive sequence of range tombstones (#10877).
|
||||
|
||||
### Behavior Changes
|
||||
* Sanitize min_write_buffer_number_to_merge to 1 if atomic flush is enabled to prevent unexpected data loss when WAL is disabled in a multi-column-family setting (#10773).
|
||||
* With periodic stat dumper waits up every options.stats_dump_period_sec seconds, it won't dump stats for a CF if it has no change in the period, unless 7 periods have been skipped.
|
||||
* Only periodic stats dumper triggered by options.stats_dump_period_sec will update stats interval. Ones triggered by DB::GetProperty() will not update stats interval and will report based on an interval since the last time stats dump period.
|
||||
|
||||
### Public API changes
|
||||
* Make kXXH3 checksum the new default, because it is faster on common hardware, especially with kCRC32c affected by a performance bug in some versions of clang (https://github.com/facebook/rocksdb/issues/9891). DBs written with this new setting can be read by RocksDB 6.27 and newer.
|
||||
|
||||
+7
-15
@@ -20,15 +20,12 @@ There are few options when compiling RocksDB:
|
||||
depend on gflags. You will need to have gflags installed to run `make all`. This will compile RocksDB in debug mode. Don't
|
||||
use binaries compiled by `make all` in production.
|
||||
|
||||
* By default the binary we produce is optimized for the CPU you're compiling on
|
||||
(`-march=native` or the equivalent). To build a binary compatible with the most
|
||||
general architecture supported by your CPU and compiler, set `PORTABLE=1` for
|
||||
the build, but performance will suffer as many operations benefit from newer
|
||||
and wider instructions. In addition to `PORTABLE=0` (default) and `PORTABLE=1`,
|
||||
it can be set to an architecture name recognized by your compiler. For example,
|
||||
on 64-bit x86, a reasonable compromise is `PORTABLE=haswell` which supports
|
||||
many or most of the available optimizations while still being compatible with
|
||||
most processors made since roughly 2013.
|
||||
* By default the binary we produce is optimized for the platform you're compiling on
|
||||
(`-march=native` or the equivalent). SSE4.2 will thus be enabled automatically if your
|
||||
CPU supports it. To print a warning if your CPU does not support SSE4.2, build with
|
||||
`USE_SSE=1 make static_lib` or, if using CMake, `cmake -DFORCE_SSE42=ON`. If you want
|
||||
to build a portable binary, add `PORTABLE=1` before your make commands, like this:
|
||||
`PORTABLE=1 make static_lib`.
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -51,11 +48,6 @@ most processors made since roughly 2013.
|
||||
* If you wish to build the RocksJava static target, then cmake is required for building Snappy.
|
||||
|
||||
* If you wish to run microbench (e.g, `make microbench`, `make ribbon_bench` or `cmake -DWITH_BENCHMARK=1`), Google benchmark >= 1.6.0 is needed.
|
||||
* You can do the following to install Google benchmark. These commands are copied from `./build_tools/ubuntu20_image/Dockerfile`:
|
||||
|
||||
`$ 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`
|
||||
|
||||
## Supported platforms
|
||||
|
||||
@@ -186,7 +178,7 @@ most processors made since roughly 2013.
|
||||
gmake rocksdbjava
|
||||
|
||||
* **iOS**:
|
||||
* Run: `TARGET_OS=IOS make static_lib`. When building the project which uses rocksdb iOS library, make sure to define an important pre-processing macros: `IOS_CROSS_COMPILE`.
|
||||
* Run: `TARGET_OS=IOS make static_lib`. When building the project which uses rocksdb iOS library, make sure to define two important pre-processing macros: `ROCKSDB_LITE` and `IOS_CROSS_COMPILE`.
|
||||
|
||||
* **Windows** (Visual Studio 2017 to up):
|
||||
* Read and follow the instructions at CMakeLists.txt
|
||||
|
||||
@@ -44,6 +44,13 @@ quoted_perl_command = $(subst ','\'',$(perl_command))
|
||||
# Set the default DEBUG_LEVEL to 1
|
||||
DEBUG_LEVEL?=1
|
||||
|
||||
# LIB_MODE says whether or not to use/build "shared" or "static" libraries.
|
||||
# Mode "static" means to link against static libraries (.a)
|
||||
# Mode "shared" means to link against shared libraries (.so, .sl, .dylib, etc)
|
||||
#
|
||||
# Set the default LIB_MODE to static
|
||||
LIB_MODE?=static
|
||||
|
||||
# OBJ_DIR is where the object files reside. Default to the current directory
|
||||
OBJ_DIR?=.
|
||||
|
||||
@@ -74,42 +81,29 @@ else ifneq ($(filter jtest rocksdbjava%, $(MAKECMDGOALS)),)
|
||||
endif
|
||||
endif
|
||||
|
||||
# LIB_MODE says whether or not to use/build "shared" or "static" libraries.
|
||||
# Mode "static" means to link against static libraries (.a)
|
||||
# Mode "shared" means to link against shared libraries (.so, .sl, .dylib, etc)
|
||||
#
|
||||
ifeq ($(DEBUG_LEVEL), 0)
|
||||
# For optimized, set the default LIB_MODE to static for code size/efficiency
|
||||
LIB_MODE?=static
|
||||
else
|
||||
# For debug, set the default LIB_MODE to shared for efficient `make check` etc.
|
||||
LIB_MODE?=shared
|
||||
$(info $$DEBUG_LEVEL is ${DEBUG_LEVEL})
|
||||
|
||||
# Lite build flag.
|
||||
LITE ?= 0
|
||||
ifeq ($(LITE), 0)
|
||||
ifneq ($(filter -DROCKSDB_LITE,$(OPT)),)
|
||||
# Be backward compatible and support older format where OPT=-DROCKSDB_LITE is
|
||||
# specified instead of LITE=1 on the command line.
|
||||
LITE=1
|
||||
endif
|
||||
else ifeq ($(LITE), 1)
|
||||
ifeq ($(filter -DROCKSDB_LITE,$(OPT)),)
|
||||
OPT += -DROCKSDB_LITE
|
||||
endif
|
||||
endif
|
||||
|
||||
$(info $$DEBUG_LEVEL is $(DEBUG_LEVEL), $$LIB_MODE is $(LIB_MODE))
|
||||
|
||||
# Detect what platform we're building on.
|
||||
# Export some common variables that might have been passed as Make variables
|
||||
# instead of environment variables.
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
|
||||
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
|
||||
export COMPILE_WITH_ASAN="$(COMPILE_WITH_ASAN)"; \
|
||||
export COMPILE_WITH_TSAN="$(COMPILE_WITH_TSAN)"; \
|
||||
export COMPILE_WITH_UBSAN="$(COMPILE_WITH_UBSAN)"; \
|
||||
export PORTABLE="$(PORTABLE)"; \
|
||||
export ROCKSDB_NO_FBCODE="$(ROCKSDB_NO_FBCODE)"; \
|
||||
export USE_CLANG="$(USE_CLANG)"; \
|
||||
export LIB_MODE="$(LIB_MODE)"; \
|
||||
export ROCKSDB_CXX_STANDARD="$(ROCKSDB_CXX_STANDARD)"; \
|
||||
export USE_FOLLY="$(USE_FOLLY)"; \
|
||||
"$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
|
||||
# Figure out optimize level.
|
||||
ifneq ($(DEBUG_LEVEL), 2)
|
||||
ifeq ($(LITE), 0)
|
||||
OPTIMIZE_LEVEL ?= -O2
|
||||
else
|
||||
OPTIMIZE_LEVEL ?= -Os
|
||||
endif
|
||||
endif
|
||||
# `OPTIMIZE_LEVEL` is empty when the user does not set it and `DEBUG_LEVEL=2`.
|
||||
# In that case, the compiler default (`-O0` for gcc and clang) will be used.
|
||||
@@ -242,6 +236,25 @@ am__v_AR_1 =
|
||||
AM_LINK = $(AM_V_CCLD)$(CXX) -L. $(patsubst lib%.a, -l%, $(patsubst lib%.$(PLATFORM_SHARED_EXT), -l%, $^)) $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
AM_SHARE = $(AM_V_CCLD) $(CXX) $(PLATFORM_SHARED_LDFLAGS)$@ -L. $(patsubst lib%.$(PLATFORM_SHARED_EXT), -l%, $^) $(EXEC_LDFLAGS) $(LDFLAGS) -o $@
|
||||
|
||||
# Detect what platform we're building on.
|
||||
# Export some common variables that might have been passed as Make variables
|
||||
# instead of environment variables.
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
|
||||
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
|
||||
export COMPILE_WITH_ASAN="$(COMPILE_WITH_ASAN)"; \
|
||||
export COMPILE_WITH_TSAN="$(COMPILE_WITH_TSAN)"; \
|
||||
export COMPILE_WITH_UBSAN="$(COMPILE_WITH_UBSAN)"; \
|
||||
export PORTABLE="$(PORTABLE)"; \
|
||||
export ROCKSDB_NO_FBCODE="$(ROCKSDB_NO_FBCODE)"; \
|
||||
export USE_CLANG="$(USE_CLANG)"; \
|
||||
export LIB_MODE="$(LIB_MODE)"; \
|
||||
export ROCKSDB_CXX_STANDARD="$(ROCKSDB_CXX_STANDARD)"; \
|
||||
export USE_FOLLY="$(USE_FOLLY)"; \
|
||||
"$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
|
||||
ROCKSDB_PLUGIN_MKS = $(foreach plugin, $(ROCKSDB_PLUGINS), plugin/$(plugin)/*.mk)
|
||||
include $(ROCKSDB_PLUGIN_MKS)
|
||||
ROCKSDB_PLUGIN_PROTO =ROCKSDB_NAMESPACE::ObjectLibrary\&, const std::string\&
|
||||
@@ -253,7 +266,6 @@ ROCKSDB_PLUGIN_EXTERNS = $(foreach p, $(ROCKSDB_PLUGIN_W_FUNCS), int $($(p)_FUNC
|
||||
ROCKSDB_PLUGIN_BUILTINS = $(foreach p, $(ROCKSDB_PLUGIN_W_FUNCS), {\"$(p)\"\, $($(p)_FUNC)}\,)
|
||||
ROCKSDB_PLUGIN_LDFLAGS = $(foreach plugin, $(ROCKSDB_PLUGINS), $($(plugin)_LDFLAGS))
|
||||
ROCKSDB_PLUGIN_PKGCONFIG_REQUIRES = $(foreach plugin, $(ROCKSDB_PLUGINS), $($(plugin)_PKGCONFIG_REQUIRES))
|
||||
ROCKSDB_PLUGIN_TESTS = $(foreach p, $(ROCKSDB_PLUGINS), $(foreach test, $($(p)_TESTS), plugin/$(p)/$(test)))
|
||||
|
||||
CXXFLAGS += $(foreach plugin, $(ROCKSDB_PLUGINS), $($(plugin)_CXXFLAGS))
|
||||
PLATFORM_LDFLAGS += $(ROCKSDB_PLUGIN_LDFLAGS)
|
||||
@@ -324,6 +336,13 @@ endif
|
||||
ifeq ($(PLATFORM), OS_SOLARIS)
|
||||
PLATFORM_CXXFLAGS += -D _GLIBCXX_USE_C99
|
||||
endif
|
||||
ifneq ($(filter -DROCKSDB_LITE,$(OPT)),)
|
||||
# found
|
||||
CFLAGS += -fno-exceptions
|
||||
CXXFLAGS += -fno-exceptions
|
||||
# LUA is not supported under ROCKSDB_LITE
|
||||
LUA_PATH =
|
||||
endif
|
||||
|
||||
ifeq ($(LIB_MODE),shared)
|
||||
# So that binaries are executable from build location, in addition to install location
|
||||
@@ -337,8 +356,8 @@ ifneq ($(MACHINE), arm64)
|
||||
# linking with jemalloc (as it won't be arm64-compatible) and remove some other options
|
||||
# set during platform detection
|
||||
DISABLE_JEMALLOC=1
|
||||
PLATFORM_CCFLAGS := $(filter-out -march=native, $(PLATFORM_CCFLAGS))
|
||||
PLATFORM_CXXFLAGS := $(filter-out -march=native, $(PLATFORM_CXXFLAGS))
|
||||
PLATFORM_CFLAGS := $(filter-out -march=native -DHAVE_SSE42 -DHAVE_AVX2, $(PLATFORM_CFLAGS))
|
||||
PLATFORM_CXXFLAGS := $(filter-out -march=native -DHAVE_SSE42 -DHAVE_AVX2, $(PLATFORM_CXXFLAGS))
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -539,7 +558,7 @@ endif
|
||||
|
||||
ifdef USE_CLANG
|
||||
# Used by some teams in Facebook
|
||||
WARNING_FLAGS += -Wshift-sign-overflow -Wambiguous-reversed-operator
|
||||
WARNING_FLAGS += -Wshift-sign-overflow
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
@@ -628,12 +647,10 @@ STRESS_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(STRESS_LIB_SOURCES))
|
||||
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 += $(TEST_MAIN_SOURCES) $(TOOL_MAIN_SOURCES) $(BENCH_MAIN_SOURCES)
|
||||
ALL_SOURCES += $(ROCKSDB_PLUGIN_SOURCES) $(ROCKSDB_PLUGIN_TESTS)
|
||||
ALL_SOURCES += $(ROCKSDB_PLUGIN_SOURCES)
|
||||
|
||||
PLUGIN_TESTS = $(patsubst %.cc, %, $(notdir $(ROCKSDB_PLUGIN_TESTS)))
|
||||
TESTS = $(patsubst %.cc, %, $(notdir $(TEST_MAIN_SOURCES)))
|
||||
TESTS += $(patsubst %.c, %, $(notdir $(TEST_MAIN_SOURCES_C)))
|
||||
TESTS += $(PLUGIN_TESTS)
|
||||
|
||||
# `make check-headers` to very that each header file includes its own
|
||||
# dependencies
|
||||
@@ -685,7 +702,6 @@ NON_PARALLEL_TEST = \
|
||||
env_test \
|
||||
deletefile_test \
|
||||
db_bloom_filter_test \
|
||||
$(PLUGIN_TESTS) \
|
||||
|
||||
PARALLEL_TEST = $(filter-out $(NON_PARALLEL_TEST), $(TESTS))
|
||||
|
||||
@@ -1062,11 +1078,13 @@ check: all
|
||||
rm -rf $(TEST_TMPDIR)
|
||||
ifneq ($(PLATFORM), OS_AIX)
|
||||
$(PYTHON) tools/check_all_python.py
|
||||
ifeq ($(filter -DROCKSDB_LITE,$(OPT)),)
|
||||
ifndef ASSERT_STATUS_CHECKED # not yet working with these tests
|
||||
$(PYTHON) tools/ldb_test.py
|
||||
sh tools/rocksdb_dump_test.sh
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
ifndef SKIP_FORMAT_BUCK_CHECKS
|
||||
$(MAKE) check-format
|
||||
$(MAKE) check-buck-targets
|
||||
@@ -1222,9 +1240,9 @@ clean: clean-ext-libraries-all clean-rocks clean-rocksjava
|
||||
clean-not-downloaded: clean-ext-libraries-bin clean-rocks clean-not-downloaded-rocksjava
|
||||
|
||||
clean-rocks:
|
||||
# Not practical to exactly match all versions/variants in naming (e.g. debug or not)
|
||||
rm -f ${LIBNAME}*.so* ${LIBNAME}*.a
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(MICROBENCHS)
|
||||
echo shared=$(ALL_SHARED_LIBS)
|
||||
echo static=$(ALL_STATIC_LIBS)
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(ALL_STATIC_LIBS) $(ALL_SHARED_LIBS) $(MICROBENCHS)
|
||||
rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
|
||||
$(FIND) . -name "*.[oda]" -exec rm -f {} \;
|
||||
$(FIND) . -type f \( -name "*.gcda" -o -name "*.gcno" \) -exec rm -f {} \;
|
||||
@@ -1337,14 +1355,6 @@ db_sanity_test: $(OBJ_DIR)/tools/db_sanity_test.o $(LIBRARY)
|
||||
db_repl_stress: $(OBJ_DIR)/tools/db_repl_stress.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
define MakeTestRule
|
||||
$(notdir $(1:%.cc=%)): $(1:%.cc=$$(OBJ_DIR)/%.o) $$(TEST_LIBRARY) $$(LIBRARY)
|
||||
$$(AM_LINK)
|
||||
endef
|
||||
|
||||
# For each PLUGIN test, create a rule to generate the test executable
|
||||
$(foreach test, $(ROCKSDB_PLUGIN_TESTS), $(eval $(call MakeTestRule, $(test))))
|
||||
|
||||
arena_test: $(OBJ_DIR)/memory/arena_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1417,9 +1427,6 @@ thread_local_test: $(OBJ_DIR)/util/thread_local_test.o $(TEST_LIBRARY) $(LIBRARY
|
||||
work_queue_test: $(OBJ_DIR)/util/work_queue_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
udt_util_test: $(OBJ_DIR)/util/udt_util_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
corruption_test: $(OBJ_DIR)/db/corruption_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1483,9 +1490,6 @@ db_compaction_filter_test: $(OBJ_DIR)/db/db_compaction_filter_test.o $(TEST_LIBR
|
||||
db_compaction_test: $(OBJ_DIR)/db/db_compaction_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_clip_test: $(OBJ_DIR)/db/db_clip_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_dynamic_level_test: $(OBJ_DIR)/db/db_dynamic_level_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -2054,7 +2058,7 @@ JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux
|
||||
ifeq ($(PLATFORM), OS_SOLARIS)
|
||||
ARCH := $(shell isainfo -b)
|
||||
else ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
ifneq (,$(filter amd64 ppc64 ppc64le s390x arm64 aarch64 sparc64 loongarch64, $(MACHINE)))
|
||||
ifneq (,$(filter amd64 ppc64 ppc64le s390x arm64 aarch64 sparc64, $(MACHINE)))
|
||||
ARCH := 64
|
||||
else
|
||||
ARCH := 32
|
||||
@@ -2075,7 +2079,7 @@ ifneq ($(origin JNI_LIBC), undefined)
|
||||
endif
|
||||
|
||||
ifeq (,$(ROCKSDBJNILIB))
|
||||
ifneq (,$(filter ppc% s390x arm64 aarch64 sparc64 loongarch64, $(MACHINE)))
|
||||
ifneq (,$(filter ppc% s390x arm64 aarch64 sparc64, $(MACHINE)))
|
||||
ROCKSDBJNILIB = librocksdbjni-linux-$(MACHINE)$(JNI_LIBC_POSTFIX).so
|
||||
else
|
||||
ROCKSDBJNILIB = librocksdbjni-linux$(ARCH)$(JNI_LIBC_POSTFIX).so
|
||||
@@ -2088,8 +2092,8 @@ ROCKSDB_JAVADOCS_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-javadoc.jar
|
||||
ROCKSDB_SOURCES_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-sources.jar
|
||||
SHA256_CMD = sha256sum
|
||||
|
||||
ZLIB_VER ?= 1.2.13
|
||||
ZLIB_SHA256 ?= b3a24de97a8fdbc835b9833169501030b8977031bcb54b3b3ac13740f846ab30
|
||||
ZLIB_VER ?= 1.2.12
|
||||
ZLIB_SHA256 ?= 91844808532e5ce316b3c010929493c0244f3d37593afd6de04f71821d5136d9
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
@@ -2441,8 +2445,6 @@ checkout_folly:
|
||||
@# NOTE: this hack is required for gcc in some cases
|
||||
perl -pi -e 's/(__has_include.<experimental.memory_resource>.)/__cpp_rtti && $$1/' third-party/folly/folly/memory/MemoryResource.h
|
||||
|
||||
CXX_M_FLAGS = $(filter -m%, $(CXXFLAGS))
|
||||
|
||||
build_folly:
|
||||
FOLLY_INST_PATH=`cd third-party/folly; $(PYTHON) build/fbcode_builder/getdeps.py show-inst-dir`; \
|
||||
if [ "$$FOLLY_INST_PATH" ]; then \
|
||||
@@ -2453,8 +2455,8 @@ build_folly:
|
||||
fi
|
||||
# Restore the original version of Invoke.h with boost dependency
|
||||
cd third-party/folly && ${GIT_COMMAND} checkout folly/functional/Invoke.h
|
||||
cd third-party/folly && \
|
||||
CXXFLAGS=" $(CXX_M_FLAGS) -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
|
||||
cd third-party/folly && MAYBE_AVX2=`echo $(CXXFLAGS) | grep -o -- -DHAVE_AVX2 | sed 's/-DHAVE_AVX2/-mavx2/g' || true` && \
|
||||
CXXFLAGS=" $$MAYBE_AVX2 -DHAVE_CXX11_ATOMIC " $(PYTHON) build/fbcode_builder/getdeps.py build --no-tests
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build size testing
|
||||
@@ -2475,6 +2477,18 @@ build_size:
|
||||
$(REPORT_BUILD_STATISTIC) rocksdb.build_size.shared_lib $$(stat --printf="%s" `readlink -f librocksdb.so`)
|
||||
strip `readlink -f librocksdb.so`
|
||||
$(REPORT_BUILD_STATISTIC) rocksdb.build_size.shared_lib_stripped $$(stat --printf="%s" `readlink -f librocksdb.so`)
|
||||
# === lite build, static ===
|
||||
$(MAKE) clean
|
||||
$(MAKE) LITE=1 static_lib
|
||||
$(REPORT_BUILD_STATISTIC) rocksdb.build_size.static_lib_lite $$(stat --printf="%s" librocksdb.a)
|
||||
strip librocksdb.a
|
||||
$(REPORT_BUILD_STATISTIC) rocksdb.build_size.static_lib_lite_stripped $$(stat --printf="%s" librocksdb.a)
|
||||
# === lite build, shared ===
|
||||
$(MAKE) clean
|
||||
$(MAKE) LITE=1 shared_lib
|
||||
$(REPORT_BUILD_STATISTIC) rocksdb.build_size.shared_lib_lite $$(stat --printf="%s" `readlink -f librocksdb.so`)
|
||||
strip `readlink -f librocksdb.so`
|
||||
$(REPORT_BUILD_STATISTIC) rocksdb.build_size.shared_lib_lite_stripped $$(stat --printf="%s" `readlink -f librocksdb.so`)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Platform-specific compilation
|
||||
|
||||
@@ -5,4 +5,3 @@ This is the list of all known third-party plugins for RocksDB. If something is m
|
||||
* [ZenFS](https://github.com/westerndigitalcorporation/zenfs): a file system for zoned block devices
|
||||
* [RADOS](https://github.com/riversand963/rocksdb-rados-env): an Env used for interacting with RADOS. Migrated from RocksDB main repo.
|
||||
* [PMEM](https://github.com/pmem/pmem-rocksdb-plugin): a collection of plugins to enable Persistent Memory on RocksDB.
|
||||
* [IPPCP](https://github.com/intel/ippcp-plugin-rocksdb): a plugin to enable encryption on RocksDB based on Intel optimized open source IPP-Crypto library.
|
||||
@@ -1,6 +1,8 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](https://circleci.com/gh/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/main)
|
||||
[](http://140-211-168-68-openstack.osuosl.org:8080/job/rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# RocksDBLite
|
||||
|
||||
RocksDBLite is a project focused on mobile use cases, which don't need a lot of fancy things we've built for server workloads and they are very sensitive to binary size. For that reason, we added a compile flag ROCKSDB_LITE that comments out a lot of the nonessential code and keeps the binary lean.
|
||||
|
||||
Some examples of the features disabled by ROCKSDB_LITE:
|
||||
* compiled-in support for LDB tool
|
||||
* No backup engine
|
||||
* No support for replication (which we provide in form of TransactionalIterator)
|
||||
* No advanced monitoring tools
|
||||
* No special-purpose memtables that are highly optimized for specific use cases
|
||||
* No Transactions
|
||||
|
||||
When adding a new big feature to RocksDB, please add ROCKSDB_LITE compile guard if:
|
||||
* Nobody from mobile really needs your feature,
|
||||
* Your feature is adding a lot of weight to the binary.
|
||||
|
||||
Don't add ROCKSDB_LITE compile guard if:
|
||||
* It would introduce a lot of code complexity. Compile guards make code harder to read. It's a trade-off.
|
||||
* Your feature is not adding a lot of weight.
|
||||
|
||||
If unsure, ask. :)
|
||||
@@ -11,15 +11,13 @@ load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrap
|
||||
cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_helpers.cc",
|
||||
"cache/cache_key.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/charged_cache.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/compressed_secondary_cache.cc",
|
||||
"cache/fast_lru_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
"cache/secondary_cache.cc",
|
||||
"cache/secondary_cache_adapter.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_contents.cc",
|
||||
@@ -106,7 +104,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"db/write_batch.cc",
|
||||
"db/write_batch_base.cc",
|
||||
"db/write_controller.cc",
|
||||
"db/write_stall_stats.cc",
|
||||
"db/write_thread.cc",
|
||||
"env/composite_env.cc",
|
||||
"env/env.cc",
|
||||
@@ -166,7 +163,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"options/options.cc",
|
||||
"options/options_helper.cc",
|
||||
"options/options_parser.cc",
|
||||
"port/mmap.cc",
|
||||
"port/port_posix.cc",
|
||||
"port/stack_trace.cc",
|
||||
"port/win/env_default.cc",
|
||||
@@ -183,7 +179,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"table/block_based/block_based_table_iterator.cc",
|
||||
"table/block_based/block_based_table_reader.cc",
|
||||
"table/block_based/block_builder.cc",
|
||||
"table/block_based/block_cache.cc",
|
||||
"table/block_based/block_prefetcher.cc",
|
||||
"table/block_based/block_prefix_index.cc",
|
||||
"table/block_based/data_block_footer.cc",
|
||||
@@ -202,7 +197,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"table/block_based/reader_common.cc",
|
||||
"table/block_based/uncompression_dict_reader.cc",
|
||||
"table/block_fetcher.cc",
|
||||
"table/compaction_merging_iterator.cc",
|
||||
"table/cuckoo/cuckoo_table_builder.cc",
|
||||
"table/cuckoo/cuckoo_table_factory.cc",
|
||||
"table/cuckoo/cuckoo_table_reader.cc",
|
||||
@@ -250,7 +244,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"util/concurrent_task_limiter_impl.cc",
|
||||
"util/crc32c.cc",
|
||||
"util/crc32c_arm64.cc",
|
||||
"util/data_structure.cc",
|
||||
"util/dynamic_bloom.cc",
|
||||
"util/file_checksum_helper.cc",
|
||||
"util/hash.cc",
|
||||
@@ -264,8 +257,6 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"util/string_util.cc",
|
||||
"util/thread_local.cc",
|
||||
"util/threadpool_imp.cc",
|
||||
"util/udt_util.cc",
|
||||
"util/write_batch_util.cc",
|
||||
"util/xxhash.cc",
|
||||
"utilities/agg_merge/agg_merge.cc",
|
||||
"utilities/backup/backup_engine.cc",
|
||||
@@ -356,14 +347,350 @@ cpp_library_wrapper(name="rocksdb_lib", srcs=[
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
], headers=None, link_whole=False, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[], deps=[":rocksdb_lib"], headers=None, link_whole=True, extra_test_libs=False)
|
||||
cpp_library_wrapper(name="rocksdb_whole_archive_lib", srcs=[
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_key.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/charged_cache.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/compressed_secondary_cache.cc",
|
||||
"cache/fast_lru_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_contents.cc",
|
||||
"db/blob/blob_fetcher.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_builder.cc",
|
||||
"db/blob/blob_file_cache.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_file_reader.cc",
|
||||
"db/blob/blob_garbage_meter.cc",
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_sequential_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
"db/blob/blob_source.cc",
|
||||
"db/blob/prefetch_buffer_collection.cc",
|
||||
"db/builder.cc",
|
||||
"db/c.cc",
|
||||
"db/column_family.cc",
|
||||
"db/compaction/compaction.cc",
|
||||
"db/compaction/compaction_iterator.cc",
|
||||
"db/compaction/compaction_job.cc",
|
||||
"db/compaction/compaction_outputs.cc",
|
||||
"db/compaction/compaction_picker.cc",
|
||||
"db/compaction/compaction_picker_fifo.cc",
|
||||
"db/compaction/compaction_picker_level.cc",
|
||||
"db/compaction/compaction_picker_universal.cc",
|
||||
"db/compaction/compaction_service_job.cc",
|
||||
"db/compaction/compaction_state.cc",
|
||||
"db/compaction/sst_partitioner.cc",
|
||||
"db/compaction/subcompaction_state.cc",
|
||||
"db/convenience.cc",
|
||||
"db/db_filesnapshot.cc",
|
||||
"db/db_impl/compacted_db_impl.cc",
|
||||
"db/db_impl/db_impl.cc",
|
||||
"db/db_impl/db_impl_compaction_flush.cc",
|
||||
"db/db_impl/db_impl_debug.cc",
|
||||
"db/db_impl/db_impl_experimental.cc",
|
||||
"db/db_impl/db_impl_files.cc",
|
||||
"db/db_impl/db_impl_open.cc",
|
||||
"db/db_impl/db_impl_readonly.cc",
|
||||
"db/db_impl/db_impl_secondary.cc",
|
||||
"db/db_impl/db_impl_write.cc",
|
||||
"db/db_info_dumper.cc",
|
||||
"db/db_iter.cc",
|
||||
"db/dbformat.cc",
|
||||
"db/error_handler.cc",
|
||||
"db/event_helpers.cc",
|
||||
"db/experimental.cc",
|
||||
"db/external_sst_file_ingestion_job.cc",
|
||||
"db/file_indexer.cc",
|
||||
"db/flush_job.cc",
|
||||
"db/flush_scheduler.cc",
|
||||
"db/forward_iterator.cc",
|
||||
"db/import_column_family_job.cc",
|
||||
"db/internal_stats.cc",
|
||||
"db/log_reader.cc",
|
||||
"db/log_writer.cc",
|
||||
"db/logs_with_prep_tracker.cc",
|
||||
"db/malloc_stats.cc",
|
||||
"db/memtable.cc",
|
||||
"db/memtable_list.cc",
|
||||
"db/merge_helper.cc",
|
||||
"db/merge_operator.cc",
|
||||
"db/output_validator.cc",
|
||||
"db/periodic_task_scheduler.cc",
|
||||
"db/range_del_aggregator.cc",
|
||||
"db/range_tombstone_fragmenter.cc",
|
||||
"db/repair.cc",
|
||||
"db/seqno_to_time_mapping.cc",
|
||||
"db/snapshot_impl.cc",
|
||||
"db/table_cache.cc",
|
||||
"db/table_properties_collector.cc",
|
||||
"db/transaction_log_impl.cc",
|
||||
"db/trim_history_scheduler.cc",
|
||||
"db/version_builder.cc",
|
||||
"db/version_edit.cc",
|
||||
"db/version_edit_handler.cc",
|
||||
"db/version_set.cc",
|
||||
"db/wal_edit.cc",
|
||||
"db/wal_manager.cc",
|
||||
"db/wide/wide_column_serialization.cc",
|
||||
"db/wide/wide_columns.cc",
|
||||
"db/write_batch.cc",
|
||||
"db/write_batch_base.cc",
|
||||
"db/write_controller.cc",
|
||||
"db/write_thread.cc",
|
||||
"env/composite_env.cc",
|
||||
"env/env.cc",
|
||||
"env/env_chroot.cc",
|
||||
"env/env_encryption.cc",
|
||||
"env/env_posix.cc",
|
||||
"env/file_system.cc",
|
||||
"env/file_system_tracer.cc",
|
||||
"env/fs_posix.cc",
|
||||
"env/fs_remap.cc",
|
||||
"env/io_posix.cc",
|
||||
"env/mock_env.cc",
|
||||
"env/unique_id_gen.cc",
|
||||
"file/delete_scheduler.cc",
|
||||
"file/file_prefetch_buffer.cc",
|
||||
"file/file_util.cc",
|
||||
"file/filename.cc",
|
||||
"file/line_file_reader.cc",
|
||||
"file/random_access_file_reader.cc",
|
||||
"file/read_write_util.cc",
|
||||
"file/readahead_raf.cc",
|
||||
"file/sequence_file_reader.cc",
|
||||
"file/sst_file_manager_impl.cc",
|
||||
"file/writable_file_writer.cc",
|
||||
"logging/auto_roll_logger.cc",
|
||||
"logging/event_logger.cc",
|
||||
"logging/log_buffer.cc",
|
||||
"memory/arena.cc",
|
||||
"memory/concurrent_arena.cc",
|
||||
"memory/jemalloc_nodump_allocator.cc",
|
||||
"memory/memkind_kmem_allocator.cc",
|
||||
"memory/memory_allocator.cc",
|
||||
"memtable/alloc_tracker.cc",
|
||||
"memtable/hash_linklist_rep.cc",
|
||||
"memtable/hash_skiplist_rep.cc",
|
||||
"memtable/skiplistrep.cc",
|
||||
"memtable/vectorrep.cc",
|
||||
"memtable/write_buffer_manager.cc",
|
||||
"monitoring/histogram.cc",
|
||||
"monitoring/histogram_windowing.cc",
|
||||
"monitoring/in_memory_stats_history.cc",
|
||||
"monitoring/instrumented_mutex.cc",
|
||||
"monitoring/iostats_context.cc",
|
||||
"monitoring/perf_context.cc",
|
||||
"monitoring/perf_level.cc",
|
||||
"monitoring/persistent_stats_history.cc",
|
||||
"monitoring/statistics.cc",
|
||||
"monitoring/thread_status_impl.cc",
|
||||
"monitoring/thread_status_updater.cc",
|
||||
"monitoring/thread_status_updater_debug.cc",
|
||||
"monitoring/thread_status_util.cc",
|
||||
"monitoring/thread_status_util_debug.cc",
|
||||
"options/cf_options.cc",
|
||||
"options/configurable.cc",
|
||||
"options/customizable.cc",
|
||||
"options/db_options.cc",
|
||||
"options/options.cc",
|
||||
"options/options_helper.cc",
|
||||
"options/options_parser.cc",
|
||||
"port/port_posix.cc",
|
||||
"port/stack_trace.cc",
|
||||
"port/win/env_default.cc",
|
||||
"port/win/env_win.cc",
|
||||
"port/win/io_win.cc",
|
||||
"port/win/port_win.cc",
|
||||
"port/win/win_logger.cc",
|
||||
"port/win/win_thread.cc",
|
||||
"table/adaptive/adaptive_table_factory.cc",
|
||||
"table/block_based/binary_search_index_reader.cc",
|
||||
"table/block_based/block.cc",
|
||||
"table/block_based/block_based_table_builder.cc",
|
||||
"table/block_based/block_based_table_factory.cc",
|
||||
"table/block_based/block_based_table_iterator.cc",
|
||||
"table/block_based/block_based_table_reader.cc",
|
||||
"table/block_based/block_builder.cc",
|
||||
"table/block_based/block_prefetcher.cc",
|
||||
"table/block_based/block_prefix_index.cc",
|
||||
"table/block_based/data_block_footer.cc",
|
||||
"table/block_based/data_block_hash_index.cc",
|
||||
"table/block_based/filter_block_reader_common.cc",
|
||||
"table/block_based/filter_policy.cc",
|
||||
"table/block_based/flush_block_policy.cc",
|
||||
"table/block_based/full_filter_block.cc",
|
||||
"table/block_based/hash_index_reader.cc",
|
||||
"table/block_based/index_builder.cc",
|
||||
"table/block_based/index_reader_common.cc",
|
||||
"table/block_based/parsed_full_filter_block.cc",
|
||||
"table/block_based/partitioned_filter_block.cc",
|
||||
"table/block_based/partitioned_index_iterator.cc",
|
||||
"table/block_based/partitioned_index_reader.cc",
|
||||
"table/block_based/reader_common.cc",
|
||||
"table/block_based/uncompression_dict_reader.cc",
|
||||
"table/block_fetcher.cc",
|
||||
"table/cuckoo/cuckoo_table_builder.cc",
|
||||
"table/cuckoo/cuckoo_table_factory.cc",
|
||||
"table/cuckoo/cuckoo_table_reader.cc",
|
||||
"table/format.cc",
|
||||
"table/get_context.cc",
|
||||
"table/iterator.cc",
|
||||
"table/merging_iterator.cc",
|
||||
"table/meta_blocks.cc",
|
||||
"table/persistent_cache_helper.cc",
|
||||
"table/plain/plain_table_bloom.cc",
|
||||
"table/plain/plain_table_builder.cc",
|
||||
"table/plain/plain_table_factory.cc",
|
||||
"table/plain/plain_table_index.cc",
|
||||
"table/plain/plain_table_key_coding.cc",
|
||||
"table/plain/plain_table_reader.cc",
|
||||
"table/sst_file_dumper.cc",
|
||||
"table/sst_file_reader.cc",
|
||||
"table/sst_file_writer.cc",
|
||||
"table/table_factory.cc",
|
||||
"table/table_properties.cc",
|
||||
"table/two_level_iterator.cc",
|
||||
"table/unique_id.cc",
|
||||
"test_util/sync_point.cc",
|
||||
"test_util/sync_point_impl.cc",
|
||||
"test_util/transaction_test_util.cc",
|
||||
"tools/dump/db_dump_tool.cc",
|
||||
"tools/io_tracer_parser_tool.cc",
|
||||
"tools/ldb_cmd.cc",
|
||||
"tools/ldb_tool.cc",
|
||||
"tools/sst_dump_tool.cc",
|
||||
"trace_replay/block_cache_tracer.cc",
|
||||
"trace_replay/io_tracer.cc",
|
||||
"trace_replay/trace_record.cc",
|
||||
"trace_replay/trace_record_handler.cc",
|
||||
"trace_replay/trace_record_result.cc",
|
||||
"trace_replay/trace_replay.cc",
|
||||
"util/async_file_reader.cc",
|
||||
"util/build_version.cc",
|
||||
"util/cleanable.cc",
|
||||
"util/coding.cc",
|
||||
"util/compaction_job_stats_impl.cc",
|
||||
"util/comparator.cc",
|
||||
"util/compression.cc",
|
||||
"util/compression_context_cache.cc",
|
||||
"util/concurrent_task_limiter_impl.cc",
|
||||
"util/crc32c.cc",
|
||||
"util/crc32c_arm64.cc",
|
||||
"util/dynamic_bloom.cc",
|
||||
"util/file_checksum_helper.cc",
|
||||
"util/hash.cc",
|
||||
"util/murmurhash.cc",
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
"util/ribbon_config.cc",
|
||||
"util/slice.cc",
|
||||
"util/status.cc",
|
||||
"util/stderr_logger.cc",
|
||||
"util/string_util.cc",
|
||||
"util/thread_local.cc",
|
||||
"util/threadpool_imp.cc",
|
||||
"util/xxhash.cc",
|
||||
"utilities/agg_merge/agg_merge.cc",
|
||||
"utilities/backup/backup_engine.cc",
|
||||
"utilities/blob_db/blob_compaction_filter.cc",
|
||||
"utilities/blob_db/blob_db.cc",
|
||||
"utilities/blob_db/blob_db_impl.cc",
|
||||
"utilities/blob_db/blob_db_impl_filesnapshot.cc",
|
||||
"utilities/blob_db/blob_dump_tool.cc",
|
||||
"utilities/blob_db/blob_file.cc",
|
||||
"utilities/cache_dump_load.cc",
|
||||
"utilities/cache_dump_load_impl.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
"utilities/checkpoint/checkpoint_impl.cc",
|
||||
"utilities/compaction_filters.cc",
|
||||
"utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc",
|
||||
"utilities/convenience/info_log_finder.cc",
|
||||
"utilities/counted_fs.cc",
|
||||
"utilities/debug.cc",
|
||||
"utilities/env_mirror.cc",
|
||||
"utilities/env_timed.cc",
|
||||
"utilities/fault_injection_env.cc",
|
||||
"utilities/fault_injection_fs.cc",
|
||||
"utilities/fault_injection_secondary_cache.cc",
|
||||
"utilities/leveldb_options/leveldb_options.cc",
|
||||
"utilities/memory/memory_util.cc",
|
||||
"utilities/merge_operators.cc",
|
||||
"utilities/merge_operators/bytesxor.cc",
|
||||
"utilities/merge_operators/max.cc",
|
||||
"utilities/merge_operators/put.cc",
|
||||
"utilities/merge_operators/sortlist.cc",
|
||||
"utilities/merge_operators/string_append/stringappend.cc",
|
||||
"utilities/merge_operators/string_append/stringappend2.cc",
|
||||
"utilities/merge_operators/uint64add.cc",
|
||||
"utilities/object_registry.cc",
|
||||
"utilities/option_change_migration/option_change_migration.cc",
|
||||
"utilities/options/options_util.cc",
|
||||
"utilities/persistent_cache/block_cache_tier.cc",
|
||||
"utilities/persistent_cache/block_cache_tier_file.cc",
|
||||
"utilities/persistent_cache/block_cache_tier_metadata.cc",
|
||||
"utilities/persistent_cache/persistent_cache_tier.cc",
|
||||
"utilities/persistent_cache/volatile_tier_impl.cc",
|
||||
"utilities/simulator_cache/cache_simulator.cc",
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/table_properties_collectors/compact_on_deletion_collector.cc",
|
||||
"utilities/trace/file_trace_reader_writer.cc",
|
||||
"utilities/trace/replayer_impl.cc",
|
||||
"utilities/transactions/lock/lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_tracker.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/concurrent_tree.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/keyrange.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/lock_request.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/locktree.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/manager.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/range_buffer.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/treenode.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/txnid_set.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/locktree/wfg.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/standalone_port.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/util/dbt.cc",
|
||||
"utilities/transactions/lock/range/range_tree/lib/util/memarena.cc",
|
||||
"utilities/transactions/lock/range/range_tree/range_tree_lock_manager.cc",
|
||||
"utilities/transactions/lock/range/range_tree/range_tree_lock_tracker.cc",
|
||||
"utilities/transactions/optimistic_transaction.cc",
|
||||
"utilities/transactions/optimistic_transaction_db_impl.cc",
|
||||
"utilities/transactions/pessimistic_transaction.cc",
|
||||
"utilities/transactions/pessimistic_transaction_db.cc",
|
||||
"utilities/transactions/snapshot_checker.cc",
|
||||
"utilities/transactions/transaction_base.cc",
|
||||
"utilities/transactions/transaction_db_mutex_impl.cc",
|
||||
"utilities/transactions/transaction_util.cc",
|
||||
"utilities/transactions/write_prepared_txn.cc",
|
||||
"utilities/transactions/write_prepared_txn_db.cc",
|
||||
"utilities/transactions/write_unprepared_txn.cc",
|
||||
"utilities/transactions/write_unprepared_txn_db.cc",
|
||||
"utilities/ttl/db_ttl_impl.cc",
|
||||
"utilities/wal_filter.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
], deps=[
|
||||
"//folly/container:f14_hash",
|
||||
"//folly/experimental/coro:blocking_wait",
|
||||
"//folly/experimental/coro:collect",
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
], headers=None, link_whole=True, extra_test_libs=False)
|
||||
|
||||
cpp_library_wrapper(name="rocksdb_test_lib", srcs=[
|
||||
"db/db_test_util.cc",
|
||||
"db/db_with_timestamp_test_util.cc",
|
||||
"table/mock_table.cc",
|
||||
"test_util/mock_time_env.cc",
|
||||
"test_util/secondary_cache_test_util.cc",
|
||||
"test_util/testharness.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
@@ -394,7 +721,6 @@ rocks_cpp_library_wrapper(name="rocksdb_stress_lib", srcs=[
|
||||
"db_stress_tool/db_stress_test_base.cc",
|
||||
"db_stress_tool/db_stress_tool.cc",
|
||||
"db_stress_tool/expected_state.cc",
|
||||
"db_stress_tool/expected_value.cc",
|
||||
"db_stress_tool/multi_ops_txns_stress.cc",
|
||||
"db_stress_tool/no_batched_ops_stress.cc",
|
||||
"test_util/testutil.cc",
|
||||
@@ -4752,12 +5078,6 @@ cpp_unittest_wrapper(name="db_bloom_filter_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_clip_test",
|
||||
srcs=["db/db_clip_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="db_compaction_filter_test",
|
||||
srcs=["db/db_compaction_filter_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
@@ -5510,12 +5830,6 @@ cpp_unittest_wrapper(name="ttl_test",
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="udt_util_test",
|
||||
srcs=["util/udt_util_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
extra_compiler_flags=[])
|
||||
|
||||
|
||||
cpp_unittest_wrapper(name="util_merge_operators_test",
|
||||
srcs=["utilities/util_merge_operators_test.cc"],
|
||||
deps=[":rocksdb_test_lib"],
|
||||
|
||||
@@ -15,28 +15,6 @@ At Facebook, we use RocksDB as storage engines in multiple data management servi
|
||||
|
||||
[2] https://code.facebook.com/posts/357056558062811/logdevice-a-distributed-data-store-for-logs/
|
||||
|
||||
## Bilibili
|
||||
[Bilibili](bilibili.com) [uses](https://www.alluxio.io/blog/when-ai-meets-alluxio-at-bilibili-building-an-efficient-ai-platform-for-data-preprocessing-and-model-training/) Alluxio to speed up its ML training workloads, and Alluxio uses RocksDB to store its filesystem metadata, so Bilibili uses RocksDB.
|
||||
|
||||
Bilibili's [real-time platform](https://www.alibabacloud.com/blog/architecture-and-practices-of-bilibilis-real-time-platform_596676) uses Flink, and uses RocksDB as Flink's state store.
|
||||
|
||||
## TikTok
|
||||
TikTok, or its parent company ByteDance, uses RocksDB as the storage engine for some storage systems, such as its distributed graph database [ByteGraph](https://vldb.org/pvldb/vol15/p3306-li.pdf).
|
||||
|
||||
Also, TikTok uses [Alluxio](alluxio.io) to [speed up Presto queries](https://www.alluxio.io/resources/videos/improving-presto-performance-with-alluxio-at-tiktok/), and Alluxio stores the files' metadata in RocksDB.
|
||||
|
||||
## FoundationDB
|
||||
[FoundationDB](https://www.foundationdb.org/) [uses](https://github.com/apple/foundationdb/blob/377f1f692da6ab2fe5bdac57035651db3e5fb66d/fdbserver/KeyValueStoreRocksDB.actor.cpp) RocksDB to implement a [key-value store interface](https://github.com/apple/foundationdb/blob/377f1f692da6ab2fe5bdac57035651db3e5fb66d/fdbserver/KeyValueStoreRocksDB.actor.cpp#L1127) in its server backend.
|
||||
|
||||
## Apple
|
||||
Apple [uses](https://opensource.apple.com/projects/foundationdb/) FoundationDB, so it also uses RocksDB.
|
||||
|
||||
## Snowflake
|
||||
Snowflake [uses](https://www.snowflake.com/blog/how-foundationdb-powers-snowflake-metadata-forward/) FoundationDB, so it also uses RocksDB.
|
||||
|
||||
## Microsoft
|
||||
The Bing search engine from Microsoft uses RocksDB as the storage engine for its web data platform: https://blogs.bing.com/Engineering-Blog/october-2021/RocksDB-in-Microsoft-Bing
|
||||
|
||||
## LinkedIn
|
||||
Two different use cases at Linkedin are using RocksDB as a storage engine:
|
||||
|
||||
@@ -48,9 +26,6 @@ Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasu
|
||||
## Yahoo
|
||||
Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa. Learn more about it here: http://yahooeng.tumblr.com/post/120730204806/sherpa-scales-new-heights
|
||||
|
||||
## Tencent
|
||||
[PaxosStore](https://github.com/Tencent/paxosstore) is a distributed database supporting WeChat. It uses RocksDB as its storage engine.
|
||||
|
||||
## Baidu
|
||||
[Apache Doris](http://doris.apache.org/master/en/) is a MPP analytical database engine released by Baidu. It [uses RocksDB](http://doris.apache.org/master/en/administrator-guide/operation/tablet-meta-tool.html) to manage its tablet's metadata.
|
||||
|
||||
@@ -104,18 +79,6 @@ quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
## TiKV
|
||||
[TiKV](https://github.com/pingcap/tikv) is a GEO-replicated, high-performance, distributed, transactional key-value database. TiKV is powered by Rust and Raft. TiKV uses RocksDB as its persistence layer.
|
||||
|
||||
## TiDB
|
||||
[TiDB](https://github.com/pingcap/tidb) uses the TiKV distributed key-value database, so it uses RocksDB.
|
||||
|
||||
## PingCAP
|
||||
[PingCAP](https://www.pingcap.com/) is the company behind TiDB, its cloud database service uses RocksDB.
|
||||
|
||||
## Apache Spark
|
||||
[Spark Structured Streaming](https://docs.databricks.com/structured-streaming/rocksdb-state-store.html) uses RocksDB as the local state store.
|
||||
|
||||
## Databricks
|
||||
[Databricks](https://www.databricks.com/) [replaces AWS RDS with TiDB](https://www.pingcap.com/case-study/how-databricks-tackles-the-scalability-limit-with-a-mysql-alternative/) for scalability, so it uses RocksDB.
|
||||
|
||||
## Apache Flink
|
||||
[Apache Flink](https://flink.apache.org/news/2016/03/08/release-1.0.0.html) uses RocksDB to store state locally on a machine.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ from util import ColorString
|
||||
# $python3 buckifier/buckify_rocksdb.py \
|
||||
# '{"fake": {
|
||||
# "extra_deps": [":test_dep", "//fakes/module:mock1"],
|
||||
# "extra_compiler_flags": ["-DFOO_BAR", "-Os"]
|
||||
# "extra_compiler_flags": ["-DROCKSDB_LITE", "-Os"]
|
||||
# }
|
||||
# }'
|
||||
# (Generated TARGETS file has test_dep and mock1 as dependencies for RocksDB
|
||||
@@ -154,9 +154,16 @@ def generate_targets(repo_path, deps_map):
|
||||
# rocksdb_whole_archive_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_whole_archive_lib",
|
||||
[],
|
||||
src_mk["LIB_SOURCES"] +
|
||||
# always add range_tree, it's only excluded on ppc64, which we don't use internally
|
||||
src_mk["RANGE_TREE_SOURCES"] + src_mk["TOOL_LIB_SOURCES"],
|
||||
deps=[
|
||||
":rocksdb_lib",
|
||||
"//folly/container:f14_hash",
|
||||
"//folly/experimental/coro:blocking_wait",
|
||||
"//folly/experimental/coro:collect",
|
||||
"//folly/experimental/coro:coroutine",
|
||||
"//folly/experimental/coro:task",
|
||||
"//folly/synchronization:distributed_mutex",
|
||||
],
|
||||
headers=None,
|
||||
extra_external_deps="",
|
||||
|
||||
@@ -63,7 +63,13 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
if [ "$LIB_MODE" == "shared" ]; then
|
||||
PIC_BUILD=1
|
||||
fi
|
||||
source "$PWD/build_tools/fbcode_config_platform010.sh"
|
||||
if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM010" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform010.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM009" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform009.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config_platform009.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Delete existing output, if it exists
|
||||
@@ -148,7 +154,7 @@ case "$TARGET_OS" in
|
||||
;;
|
||||
IOS)
|
||||
PLATFORM=IOS
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DOS_MACOSX -DIOS_CROSS_COMPILE "
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DOS_MACOSX -DIOS_CROSS_COMPILE -DROCKSDB_LITE"
|
||||
PLATFORM_SHARED_EXT=dylib
|
||||
PLATFORM_SHARED_LDFLAGS="-dynamiclib -install_name "
|
||||
CROSS_COMPILE=true
|
||||
@@ -419,7 +425,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_JEMALLOC; then
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o test.o -ljemalloc \
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o -ljemalloc \
|
||||
2>/dev/null; then
|
||||
# This will enable some preprocessor identifiers in the Makefile
|
||||
JEMALLOC=1
|
||||
@@ -428,19 +434,12 @@ EOF
|
||||
WITH_JEMALLOC_FLAG=1
|
||||
# check for JEMALLOC installed with HomeBrew
|
||||
if [ "$PLATFORM" == "OS_MACOSX" ]; then
|
||||
if [ "$TARGET_ARCHITECTURE" = "arm64" ]; then
|
||||
# on M1 Macs, homebrew installs here instead of /usr/local
|
||||
JEMALLOC_PREFIX="/opt/homebrew"
|
||||
else
|
||||
JEMALLOC_PREFIX="/usr/local"
|
||||
fi
|
||||
if hash brew 2>/dev/null && brew ls --versions jemalloc > /dev/null; then
|
||||
JEMALLOC_VER=$(brew ls --versions jemalloc | tail -n 1 | cut -f 2 -d ' ')
|
||||
JEMALLOC_INCLUDE="-I${JEMALLOC_PREFIX}/Cellar/jemalloc/${JEMALLOC_VER}/include"
|
||||
JEMALLOC_LIB="${JEMALLOC_PREFIX}/Cellar/jemalloc/${JEMALLOC_VER}/lib/libjemalloc_pic.a"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -L${JEMALLOC_PREFIX}/lib $JEMALLOC_LIB"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -L${JEMALLOC_PREFIX}/lib $JEMALLOC_LIB"
|
||||
JAVA_STATIC_LDFLAGS="$JAVA_STATIC_LDFLAGS -L${JEMALLOC_PREFIX}/lib $JEMALLOC_LIB"
|
||||
JEMALLOC_INCLUDE="-I/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/include"
|
||||
JEMALLOC_LIB="/usr/local/Cellar/jemalloc/${JEMALLOC_VER}/lib/libjemalloc_pic.a"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS $JEMALLOC_LIB"
|
||||
JAVA_STATIC_LDFLAGS="$JAVA_STATIC_LDFLAGS $JEMALLOC_LIB"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -628,7 +627,7 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PORTABLE" == "" ] || [ "$PORTABLE" == 0 ]; then
|
||||
if test "0$PORTABLE" -eq 0; then
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^ppc64`"; then
|
||||
# Tune for this POWER processor, treating '+' models as base models
|
||||
POWER=`LD_SHOW_AUXV=1 /bin/true | grep AT_PLATFORM | grep -E -o power[0-9]+`
|
||||
@@ -651,36 +650,37 @@ if [ "$PORTABLE" == "" ] || [ "$PORTABLE" == 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
elif [ "$TARGET_OS" == "IOS" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif [ "$TARGET_OS" == "AIX" ] || [ "$TARGET_OS" == "SunOS" ]; then
|
||||
# TODO: Not sure why we don't use -march=native on these OSes
|
||||
if test "$USE_SSE"; then
|
||||
TRY_SSE_ETC="1"
|
||||
fi
|
||||
else
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=native "
|
||||
fi
|
||||
else
|
||||
# PORTABLE specified
|
||||
if [ "$PORTABLE" == 1 ]; then
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z196 "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^riscv64`"; then
|
||||
RISC_ISA=$(cat /proc/cpuinfo | grep isa | head -1 | cut --delimiter=: -f 2 | cut -b 2-)
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
elif test "$USE_SSE"; then
|
||||
# USE_SSE is DEPRECATED
|
||||
# This is a rough approximation of the old USE_SSE behavior
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=haswell"
|
||||
fi
|
||||
# Other than those cases, not setting -march= here.
|
||||
else
|
||||
# Assume PORTABLE is a minimum assumed cpu type, e.g. PORTABLE=haswell
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${PORTABLE}"
|
||||
# PORTABLE=1
|
||||
if test "$USE_SSE"; then
|
||||
TRY_SSE_ETC="1"
|
||||
fi
|
||||
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z196 "
|
||||
fi
|
||||
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^riscv64`"; then
|
||||
RISC_ISA=$(cat /proc/cpuinfo | grep isa | head -1 | cut --delimiter=: -f 2 | cut -b 2-)
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=${RISC_ISA}"
|
||||
fi
|
||||
|
||||
if [[ "${PLATFORM}" == "OS_MACOSX" ]]; then
|
||||
# For portability compile for macOS 10.14 or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.14"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.14"
|
||||
# For portability compile for macOS 10.13 (2017) or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.13"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.13"
|
||||
# -mmacosx-version-min must come first here.
|
||||
PLATFORM_SHARED_LDFLAGS="-mmacosx-version-min=10.14 $PLATFORM_SHARED_LDFLAGS"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.14"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.14"
|
||||
PLATFORM_SHARED_LDFLAGS="-mmacosx-version-min=10.13 $PLATFORM_SHARED_LDFLAGS"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.13"
|
||||
JAVA_STATIC_DEPS_LDFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
JAVA_STATIC_DEPS_CCFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
JAVA_STATIC_DEPS_CXXFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
@@ -704,6 +704,101 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
if test "$TRY_SSE_ETC"; then
|
||||
# The USE_SSE flag now means "attempt to compile with widely-available
|
||||
# Intel architecture extensions utilized by specific optimizations in the
|
||||
# source code." It's a qualifier on PORTABLE=1 that means "mostly portable."
|
||||
# It doesn't even really check that your current CPU is compatible.
|
||||
#
|
||||
# SSE4.2 available since nehalem, ca. 2008-2010
|
||||
# Includes POPCNT for BitsSetToOne, BitParity
|
||||
TRY_SSE42="-msse4.2"
|
||||
# PCLMUL available since westmere, ca. 2010-2011
|
||||
TRY_PCLMUL="-mpclmul"
|
||||
# AVX2 available since haswell, ca. 2013-2015
|
||||
TRY_AVX2="-mavx2"
|
||||
# BMI available since haswell, ca. 2013-2015
|
||||
# Primarily for TZCNT for CountTrailingZeroBits
|
||||
TRY_BMI="-mbmi"
|
||||
# LZCNT available since haswell, ca. 2013-2015
|
||||
# For FloorLog2
|
||||
TRY_LZCNT="-mlzcnt"
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_SSE42 -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <nmmintrin.h>
|
||||
int main() {
|
||||
volatile uint32_t x = _mm_crc32_u32(0, 0);
|
||||
(void)x;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_SSE42 -DHAVE_SSE42"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use SSE intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_PCLMUL -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <wmmintrin.h>
|
||||
int main() {
|
||||
const auto a = _mm_set_epi64x(0, 0);
|
||||
const auto b = _mm_set_epi64x(0, 0);
|
||||
const auto c = _mm_clmulepi64_si128(a, b, 0x00);
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
(void)d;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_PCLMUL -DHAVE_PCLMUL"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use PCLMUL intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_AVX2 -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main() {
|
||||
const auto a = _mm256_setr_epi32(0, 1, 2, 3, 4, 7, 6, 5);
|
||||
const auto b = _mm256_permutevar8x32_epi32(a, a);
|
||||
(void)b;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_AVX2 -DHAVE_AVX2"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use AVX2 intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_BMI -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argv;
|
||||
return (int)_tzcnt_u64((uint64_t)argc);
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_BMI -DHAVE_BMI"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use BMI intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_LZCNT -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main(int argc, char *argv[]) {
|
||||
(void)argv;
|
||||
return (int)_lzcnt_u64((uint64_t)argc);
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS $TRY_LZCNT -DHAVE_LZCNT"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use LZCNT intrinsics, disabling" >&2
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
int main() {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/1795efe5f06778c15a92c8f9a2aba5dc496d9d4d/9.x/centos7-native/3bed279
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/7318eaac22659b6ff2fe43918e4b69fd0772a8a7/9.0.0/platform009/651ee30
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/4959b39cfbe5965a37c861c4c327fa7c5c759b87/9.x/platform009/9202ce7
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/45ce3375cdc77ecb2520bbf8f0ecddd3f98efd7a/2.30/platform009/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/be4de3205e029101b18aa8103daa696c2bef3b19/1.1.3/platform009/7f3b187
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/3c160ac5c67e257501e24c6c1d00ad5e01d73db6/1.2.8/platform009/7f3b187
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/73a237ac5bc0a5f5d67b39b8d253cfebaab88684/1.0.6/platform009/7f3b187
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/6ca38d3c390be2774d61a300f151464bbd632d62/1.9.1/platform009/7f3b187
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/64c58a207d2495e83abc57a500a956df09b79a7c/1.4.x/platform009/ba86d1f
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/824d0a8a5abb5b121afd1b35fc3896407ea50092/2.2.0/platform009/7f3b187
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/b62912d333ef33f9760efa6219dbe3fe6abb3b0e/master/platform009/c305944
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/0af65f71e23a67bf65dc91b11f95caa39325c432/2.0.11/platform009/7f3b187
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/02486dac347645d31dce116f44e1de3177315be2/1.4/platform009/5191652
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/2e0ec671e550bfca347300bf3f789d9c0fff24ad/2018_U5/platform009/7f3b187
|
||||
LIBURING_BASE=/mnt/gvfs/third-party2/liburing/70dbd9cfee63a25611417d09433a86d7711b3990/20200729/platform009/7f3b187
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/32b8a2407b634df3f8f948ba373fc4acc6a18296/fb/platform009/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/08634589372fa5f237bfd374e8c644a8364e78c1/2.32/platform009/ba86d1f/
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/6ae525939ad02e5e676855082fbbc7828dbafeac/3.15.0/platform009/7f3b187
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/162efd9561a3d21f6869f4814011e9cf1b3ff4dc/5.3.4/platform009/a6271c4
|
||||
BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/30bf49ad6414325e17f3425b0edcb64239427ae3/1.6.1/platform009/7f3b187
|
||||
GLOG_BASE=/mnt/gvfs/third-party2/glog/32d751bd5673375b438158717ab6a57c1cc57e3d/0.3.2_fb/platform009/10a364d
|
||||
@@ -147,7 +147,7 @@ else
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#!/bin/sh
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Set environment variables so that we can compile rocksdb using
|
||||
# fbcode settings. It uses the latest g++ and clang compilers and also
|
||||
# uses jemalloc
|
||||
# Environment variables that change the behavior of this script:
|
||||
# PIC_BUILD -- if true, it will only take pic versions of libraries from fbcode. libraries that don't have pic variant will not be included
|
||||
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies_platform009.sh"
|
||||
|
||||
CFLAGS=""
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/9.3.0 -I $LIBGCC_BASE/include/c++/9.3.0/backward"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
MAYBE_PIC=
|
||||
else
|
||||
MAYBE_PIC=_pic
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SNAPPY; then
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZLIB; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BZIP; then
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_LZ4; then
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZSTD; then
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DZSTD"
|
||||
fi
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
BENCHMARK_INCLUDE=" -I $BENCHMARK_BASE/include/"
|
||||
BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark${MAYBE_PIC}.a"
|
||||
|
||||
GLOG_INCLUDE=" -I $GLOG_BASE/include/"
|
||||
GLOG_LIBS=" $GLOG_BASE/lib/libglog${MAYBE_PIC}.a"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc${MAYBE_PIC}.a"
|
||||
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind${MAYBE_PIC}.a"
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# location of LIBURING
|
||||
LIBURING_INCLUDE=" -isystem $LIBURING_BASE/include/"
|
||||
LIBURING_LIBS="$LIBURING_BASE/lib/liburing${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DLIBURING"
|
||||
|
||||
test "$USE_SSE" || USE_SSE=1
|
||||
export USE_SSE
|
||||
test "$PORTABLE" || PORTABLE=1
|
||||
export PORTABLE
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
AS="$BINUTILS/as"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE $GLOG_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_SRC="$CLANG_BASE/../../src"
|
||||
|
||||
CLANG_ANALYZER="$CLANG_BIN/clang++"
|
||||
CLANG_SCAN_BUILD="$CLANG_SRC/llvm/clang/tools/scan-build/bin/scan-build"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
AR="$GCC_BASE/bin/gcc-ar"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/stable/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
AR="$CLANG_BIN/llvm-ar"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/9.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/9.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
CFLAGS+=" -Wno-expansion-to-defined "
|
||||
CXXFLAGS="-nostdinc++"
|
||||
fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42 -DROCKSDB_IOURING_PRESENT"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform009/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform009/lib"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=$GCC_BASE/lib64"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
PLATFORM_LDFLAGS+=" -B$BINUTILS"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
# lua not supported because it's on track for deprecation, I think
|
||||
LUA_PATH=
|
||||
LUA_LIB=
|
||||
|
||||
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -154,7 +154,7 @@ CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE/linux "
|
||||
CFLAGS+=" -isystem $KERNEL_HEADERS_INCLUDE "
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_IOURING_PRESENT"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DHAVE_SSE42 -DROCKSDB_IOURING_PRESENT"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
|
||||
@@ -360,7 +360,7 @@ function send_to_ods {
|
||||
echo >&2 "ERROR: Key $key doesn't have a value."
|
||||
return
|
||||
fi
|
||||
curl --silent "https://www.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=$key&value=$value" \
|
||||
curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=$key&value=$value" \
|
||||
--connect-timeout 60
|
||||
}
|
||||
|
||||
|
||||
@@ -104,3 +104,46 @@ get_lib_base valgrind LATEST platform010
|
||||
get_lib_base lua 5.3.4 platform010
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
|
||||
###########################################################
|
||||
# platform009 dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_platform009.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/9.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/9.0.0/platform009/*/`
|
||||
|
||||
log_header
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 9.x platform009
|
||||
get_lib_base glibc 2.30 platform009
|
||||
get_lib_base snappy LATEST platform009
|
||||
get_lib_base zlib LATEST platform009
|
||||
get_lib_base bzip2 LATEST platform009
|
||||
get_lib_base lz4 LATEST platform009
|
||||
get_lib_base zstd LATEST platform009
|
||||
get_lib_base gflags LATEST platform009
|
||||
get_lib_base jemalloc LATEST platform009
|
||||
get_lib_base numa LATEST platform009
|
||||
get_lib_base libunwind LATEST platform009
|
||||
get_lib_base tbb 2018_U5 platform009
|
||||
get_lib_base liburing LATEST platform009
|
||||
get_lib_base benchmark LATEST platform009
|
||||
|
||||
get_lib_base kernel-headers fb platform009
|
||||
get_lib_base binutils LATEST centos7-native
|
||||
get_lib_base valgrind LATEST platform009
|
||||
get_lib_base lua 5.3.4 platform009
|
||||
|
||||
git diff $OUTPUT
|
||||
Vendored
+15
-44
@@ -16,8 +16,7 @@
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
const Cache::CacheItemHelper kNoopCacheItemHelper{};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
lru_cache_options_type_info = {
|
||||
{"capacity",
|
||||
@@ -65,6 +64,7 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
};
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status SecondaryCache::CreateFromString(
|
||||
const ConfigOptions& config_options, const std::string& value,
|
||||
@@ -75,6 +75,7 @@ Status SecondaryCache::CreateFromString(
|
||||
Status status;
|
||||
std::shared_ptr<SecondaryCache> sec_cache;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
CompressedSecondaryCacheOptions sec_cache_opts;
|
||||
status = OptionTypeInfo::ParseStruct(config_options, "",
|
||||
&comp_sec_cache_options_type_info, "",
|
||||
@@ -83,13 +84,19 @@ Status SecondaryCache::CreateFromString(
|
||||
sec_cache = NewCompressedSecondaryCache(sec_cache_opts);
|
||||
}
|
||||
|
||||
#else
|
||||
(void)config_options;
|
||||
status = Status::NotSupported(
|
||||
"Cannot load compressed secondary cache in LITE mode ", args);
|
||||
#endif //! ROCKSDB_LITE
|
||||
|
||||
if (status.ok()) {
|
||||
result->swap(sec_cache);
|
||||
}
|
||||
return status;
|
||||
} else {
|
||||
return LoadSharedObject<SecondaryCache>(config_options, value, result);
|
||||
return LoadSharedObject<SecondaryCache>(config_options, value, nullptr,
|
||||
result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +108,7 @@ Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
if (value.find('=') == std::string::npos) {
|
||||
cache = NewLRUCache(ParseSizeT(value));
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
LRUCacheOptions cache_opts;
|
||||
status = OptionTypeInfo::ParseStruct(config_options, "",
|
||||
&lru_cache_options_type_info, "",
|
||||
@@ -108,51 +116,14 @@ Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
if (status.ok()) {
|
||||
cache = NewLRUCache(cache_opts);
|
||||
}
|
||||
#else
|
||||
(void)config_options;
|
||||
status = Status::NotSupported("Cannot load cache in LITE mode ", value);
|
||||
#endif //! ROCKSDB_LITE
|
||||
}
|
||||
if (status.ok()) {
|
||||
result->swap(cache);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool Cache::AsyncLookupHandle::IsReady() {
|
||||
return pending_handle == nullptr || pending_handle->IsReady();
|
||||
}
|
||||
|
||||
bool Cache::AsyncLookupHandle::IsPending() { return pending_handle != nullptr; }
|
||||
|
||||
Cache::Handle* Cache::AsyncLookupHandle::Result() {
|
||||
assert(!IsPending());
|
||||
return result_handle;
|
||||
}
|
||||
|
||||
void Cache::StartAsyncLookup(AsyncLookupHandle& async_handle) {
|
||||
async_handle.found_dummy_entry = false; // in case re-used
|
||||
assert(!async_handle.IsPending());
|
||||
async_handle.result_handle =
|
||||
Lookup(async_handle.key, async_handle.helper, async_handle.create_context,
|
||||
async_handle.priority, async_handle.stats);
|
||||
}
|
||||
|
||||
Cache::Handle* Cache::Wait(AsyncLookupHandle& async_handle) {
|
||||
WaitAll(&async_handle, 1);
|
||||
return async_handle.Result();
|
||||
}
|
||||
|
||||
void Cache::WaitAll(AsyncLookupHandle* async_handles, size_t count) {
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (async_handles[i].IsPending()) {
|
||||
// If a pending handle gets here, it should be marked at "to be handled
|
||||
// by a caller" by that caller erasing the pending_cache on it.
|
||||
assert(async_handles[i].pending_cache == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Cache::SetEvictionCallback(EvictionCallback&& fn) {
|
||||
// Overwriting non-empty with non-empty could indicate a bug
|
||||
assert(!eviction_callback_ || !fn);
|
||||
eviction_callback_ = std::move(fn);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+106
-193
@@ -13,10 +13,11 @@
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include "cache/fast_lru_cache.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "monitoring/histogram.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -31,7 +32,6 @@
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "util/stderr_logger.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -50,11 +50,8 @@ DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 2000000U, "Number of operations per thread.");
|
||||
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_uint32(skew, 5, "Degree of skew in key selection. 0 = no skew");
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
@@ -75,25 +72,17 @@ DEFINE_uint32(
|
||||
|
||||
DEFINE_uint32(gather_stats_entries_per_lock, 256,
|
||||
"For Cache::ApplyToAllEntries");
|
||||
DEFINE_bool(skewed, false, "If true, skew the key access distribution");
|
||||
|
||||
DEFINE_bool(lean, false,
|
||||
"If true, no additional computation is performed besides cache "
|
||||
"operations.");
|
||||
|
||||
DEFINE_bool(early_exit, false,
|
||||
"Exit before deallocating most memory. Good for malloc stats, e.g."
|
||||
"MALLOC_CONF=\"stats_print:true\"");
|
||||
|
||||
DEFINE_bool(histograms, true,
|
||||
"Whether to track and print histogram statistics.");
|
||||
|
||||
DEFINE_bool(report_problems, true, "Whether to ReportProblems() at the end.");
|
||||
|
||||
DEFINE_uint32(seed, 0, "Hashing/random seed to use. 0 = choose at random");
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
DEFINE_string(secondary_cache_uri, "",
|
||||
"Full URI for creating a custom secondary cache object");
|
||||
static class std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> secondary_cache;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
DEFINE_string(cache_type, "lru_cache", "Type of block cache.");
|
||||
|
||||
@@ -159,6 +148,9 @@ class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
@@ -181,27 +173,15 @@ class SharedState {
|
||||
|
||||
bool Started() const { return start_; }
|
||||
|
||||
void AddLookupStats(uint64_t hits, uint64_t misses) {
|
||||
MutexLock l(&mu_);
|
||||
lookup_count_ += hits + misses;
|
||||
lookup_hits_ += hits;
|
||||
}
|
||||
|
||||
double GetLookupHitRatio() const {
|
||||
return 1.0 * lookup_hits_ / lookup_count_;
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
uint64_t num_initialized_ = 0;
|
||||
bool start_ = false;
|
||||
uint64_t num_done_ = 0;
|
||||
uint64_t lookup_count_ = 0;
|
||||
uint64_t lookup_hits_ = 0;
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
@@ -213,19 +193,27 @@ struct ThreadState {
|
||||
uint64_t duration_us = 0;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(FLAGS_seed + 1 + index), shared(_shared) {}
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, uint32_t skew) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, int max_log) {
|
||||
uint64_t key = 0;
|
||||
if (!FLAGS_skewed) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
key = FastRange64(raw, max_key);
|
||||
} else {
|
||||
key = rnd.Skewed(max_log);
|
||||
if (key > max_key) {
|
||||
key -= max_key;
|
||||
}
|
||||
}
|
||||
uint64_t key = FastRange64(raw, max_key);
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
@@ -239,7 +227,7 @@ struct KeyGen {
|
||||
}
|
||||
};
|
||||
|
||||
Cache::ObjectPtr createValue(Random64& rnd) {
|
||||
char* createValue(Random64& rnd) {
|
||||
char* rv = new char[FLAGS_value_bytes];
|
||||
// Fill with some filler data, and take some CPU time
|
||||
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
|
||||
@@ -249,36 +237,28 @@ Cache::ObjectPtr createValue(Random64& rnd) {
|
||||
}
|
||||
|
||||
// Callbacks for secondary cache
|
||||
size_t SizeFn(Cache::ObjectPtr /*obj*/) { return FLAGS_value_bytes; }
|
||||
size_t SizeFn(void* /*obj*/) { return FLAGS_value_bytes; }
|
||||
|
||||
Status SaveToFn(Cache::ObjectPtr from_obj, size_t /*from_offset*/,
|
||||
size_t length, char* out) {
|
||||
memcpy(out, from_obj, length);
|
||||
Status SaveToFn(void* obj, size_t /*offset*/, size_t size, void* out) {
|
||||
memcpy(out, obj, size);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status CreateFn(const Slice& data, Cache::CreateContext* /*context*/,
|
||||
MemoryAllocator* /*allocator*/, Cache::ObjectPtr* out_obj,
|
||||
size_t* out_charge) {
|
||||
*out_obj = new char[data.size()];
|
||||
memcpy(*out_obj, data.data(), data.size());
|
||||
*out_charge = data.size();
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
void DeleteFn(Cache::ObjectPtr value, MemoryAllocator* /*alloc*/) {
|
||||
// Different deleters to simulate using deleter to gather
|
||||
// stats on the code origin and kind of cache entries.
|
||||
void deleter1(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
void deleter2(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
void deleter3(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
|
||||
Cache::CacheItemHelper helper1_wos(CacheEntryRole::kDataBlock, DeleteFn);
|
||||
Cache::CacheItemHelper helper1(CacheEntryRole::kDataBlock, DeleteFn, SizeFn,
|
||||
SaveToFn, CreateFn, &helper1_wos);
|
||||
Cache::CacheItemHelper helper2_wos(CacheEntryRole::kIndexBlock, DeleteFn);
|
||||
Cache::CacheItemHelper helper2(CacheEntryRole::kIndexBlock, DeleteFn, SizeFn,
|
||||
SaveToFn, CreateFn, &helper2_wos);
|
||||
Cache::CacheItemHelper helper3_wos(CacheEntryRole::kFilterBlock, DeleteFn);
|
||||
Cache::CacheItemHelper helper3(CacheEntryRole::kFilterBlock, DeleteFn, SizeFn,
|
||||
SaveToFn, CreateFn, &helper3_wos);
|
||||
Cache::CacheItemHelper helper1(SizeFn, SaveToFn, deleter1);
|
||||
Cache::CacheItemHelper helper2(SizeFn, SaveToFn, deleter2);
|
||||
Cache::CacheItemHelper helper3(SizeFn, SaveToFn, deleter3);
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
@@ -296,38 +276,36 @@ class CacheBench {
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent) {
|
||||
kHundredthUint64 * FLAGS_erase_percent),
|
||||
skewed_(FLAGS_skewed) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
max_log_ = 0;
|
||||
if (skewed_) {
|
||||
uint64_t max_key = max_key_;
|
||||
while (max_key >>= 1) max_log_++;
|
||||
if (max_key > (static_cast<uint64_t>(1) << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
if (FLAGS_cache_type == "clock_cache") {
|
||||
fprintf(stderr, "Old clock cache implementation has been removed.\n");
|
||||
exit(1);
|
||||
} else if (EndsWith(FLAGS_cache_type, "hyper_clock_cache")) {
|
||||
HyperClockCacheOptions opts(
|
||||
FLAGS_cache_size, /*estimated_entry_charge=*/0, FLAGS_num_shard_bits);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
if (FLAGS_cache_type == "fixed_hyper_clock_cache" ||
|
||||
FLAGS_cache_type == "hyper_clock_cache") {
|
||||
opts.estimated_entry_charge = FLAGS_value_bytes_estimate > 0
|
||||
? FLAGS_value_bytes_estimate
|
||||
: FLAGS_value_bytes;
|
||||
} else if (FLAGS_cache_type == "auto_hyper_clock_cache") {
|
||||
if (FLAGS_value_bytes_estimate > 0) {
|
||||
opts.min_avg_entry_charge = FLAGS_value_bytes_estimate;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.");
|
||||
exit(1);
|
||||
}
|
||||
cache_ = opts.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "hyper_clock_cache") {
|
||||
cache_ = HyperClockCacheOptions(FLAGS_cache_size, FLAGS_value_bytes,
|
||||
FLAGS_num_shard_bits)
|
||||
.MakeSharedCache();
|
||||
} else if (FLAGS_cache_type == "fast_lru_cache") {
|
||||
cache_ = NewFastLRUCache(
|
||||
FLAGS_cache_size, FLAGS_value_bytes, FLAGS_num_shard_bits,
|
||||
false /*strict_capacity_limit*/, kDefaultCacheMetadataChargePolicy);
|
||||
} else if (FLAGS_cache_type == "lru_cache") {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits,
|
||||
false /* strict_capacity_limit */,
|
||||
0.5 /* high_pri_pool_ratio */);
|
||||
opts.hash_seed = BitwiseAnd(FLAGS_seed, INT32_MAX);
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
@@ -340,6 +318,7 @@ class CacheBench {
|
||||
}
|
||||
opts.secondary_cache = secondary_cache;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
cache_ = NewLRUCache(opts);
|
||||
} else {
|
||||
@@ -351,50 +330,13 @@ class CacheBench {
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(FLAGS_seed);
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
size_t max_occ = 0;
|
||||
size_t inserts_since_max_occ_increase = 0;
|
||||
size_t keys_since_last_not_found = 0;
|
||||
|
||||
// Avoid redundant insertions by checking Lookup before Insert.
|
||||
// Loop until insertions consistently fail to increase max occupancy or
|
||||
// it becomes difficult to find keys not already inserted.
|
||||
while (inserts_since_max_occ_increase < 100 &&
|
||||
keys_since_last_not_found < 100) {
|
||||
Slice key = keygen.GetRand(rnd, max_key_, FLAGS_skew);
|
||||
|
||||
Cache::Handle* handle = cache_->Lookup(key);
|
||||
if (handle != nullptr) {
|
||||
cache_->Release(handle);
|
||||
++keys_since_last_not_found;
|
||||
continue;
|
||||
}
|
||||
keys_since_last_not_found = 0;
|
||||
|
||||
Status s =
|
||||
cache_->Insert(key, createValue(rnd), &helper1, FLAGS_value_bytes);
|
||||
for (uint64_t i = 0; i < 2 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
Status s = cache_->Insert(keygen.GetRand(rnd, max_key_, max_log_),
|
||||
createValue(rnd), &helper1, FLAGS_value_bytes);
|
||||
assert(s.ok());
|
||||
|
||||
handle = cache_->Lookup(key);
|
||||
if (!handle) {
|
||||
fprintf(stderr, "Failed to lookup key just inserted.\n");
|
||||
assert(false);
|
||||
exit(42);
|
||||
} else {
|
||||
cache_->Release(handle);
|
||||
}
|
||||
|
||||
size_t occ = cache_->GetOccupancyCount();
|
||||
if (occ > max_occ) {
|
||||
max_occ = occ;
|
||||
inserts_since_max_occ_increase = 0;
|
||||
} else {
|
||||
++inserts_since_max_occ_increase;
|
||||
}
|
||||
}
|
||||
printf("Population complete (%zu entries, %g average charge)\n", max_occ,
|
||||
1.0 * FLAGS_cache_size / max_occ);
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
@@ -453,33 +395,19 @@ class CacheBench {
|
||||
FLAGS_ops_per_thread / elapsed_secs);
|
||||
printf("Thread ops/sec = %u\n", ops_per_sec);
|
||||
|
||||
printf("Lookup hit ratio: %g\n", shared.GetLookupHitRatio());
|
||||
printf("\nOperation latency (ns):\n");
|
||||
HistogramImpl combined;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
combined.Merge(threads[i]->latency_ns_hist);
|
||||
}
|
||||
printf("%s", combined.ToString().c_str());
|
||||
|
||||
size_t occ = cache_->GetOccupancyCount();
|
||||
size_t slot = cache_->GetTableAddressCount();
|
||||
printf("Final load factor: %g (%zu / %zu)\n", 1.0 * occ / slot, occ, slot);
|
||||
|
||||
if (FLAGS_histograms) {
|
||||
printf("\nOperation latency (ns):\n");
|
||||
HistogramImpl combined;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
combined.Merge(threads[i]->latency_ns_hist);
|
||||
}
|
||||
printf("%s", combined.ToString().c_str());
|
||||
|
||||
if (FLAGS_gather_stats) {
|
||||
printf("\nGather stats latency (us):\n");
|
||||
printf("%s", stats_hist.ToString().c_str());
|
||||
}
|
||||
if (FLAGS_gather_stats) {
|
||||
printf("\nGather stats latency (us):\n");
|
||||
printf("%s", stats_hist.ToString().c_str());
|
||||
}
|
||||
|
||||
if (FLAGS_report_problems) {
|
||||
printf("\n");
|
||||
std::shared_ptr<Logger> logger =
|
||||
std::make_shared<StderrLogger>(InfoLogLevel::DEBUG_LEVEL);
|
||||
cache_->ReportProblems(logger);
|
||||
}
|
||||
printf("%s", stats_report.c_str());
|
||||
printf("\n%s", stats_report.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -492,6 +420,8 @@ class CacheBench {
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
const bool skewed_;
|
||||
int max_log_;
|
||||
|
||||
// A benchmark version of gathering stats on an active block cache by
|
||||
// iterating over it. The primary purpose is to measure the impact of
|
||||
@@ -511,7 +441,7 @@ class CacheBench {
|
||||
uint64_t total_entry_count = 0;
|
||||
uint64_t table_occupancy = 0;
|
||||
uint64_t table_size = 0;
|
||||
std::set<const Cache::CacheItemHelper*> helpers;
|
||||
std::set<Cache::DeleterFn> deleters;
|
||||
StopWatchNano timer(clock);
|
||||
|
||||
for (;;) {
|
||||
@@ -524,7 +454,7 @@ class CacheBench {
|
||||
for (;;) {
|
||||
if (shared->AllDone()) {
|
||||
std::ostringstream ostr;
|
||||
ostr << "\nMost recent cache entry stats:\n"
|
||||
ostr << "Most recent cache entry stats:\n"
|
||||
<< "Number of entries: " << total_entry_count << "\n"
|
||||
<< "Table occupancy: " << table_occupancy << " / "
|
||||
<< table_size << " = "
|
||||
@@ -536,7 +466,7 @@ class CacheBench {
|
||||
<< BytesToHumanString(static_cast<uint64_t>(
|
||||
1.0 * total_charge / total_entry_count))
|
||||
<< "\n"
|
||||
<< "Unique helpers: " << helpers.size() << "\n";
|
||||
<< "Unique deleters: " << deleters.size() << "\n";
|
||||
*stats_report = ostr.str();
|
||||
return;
|
||||
}
|
||||
@@ -552,26 +482,22 @@ class CacheBench {
|
||||
total_key_size = 0;
|
||||
total_charge = 0;
|
||||
total_entry_count = 0;
|
||||
helpers.clear();
|
||||
auto fn = [&](const Slice& key, Cache::ObjectPtr /*value*/, size_t charge,
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
deleters.clear();
|
||||
auto fn = [&](const Slice& key, void* /*value*/, size_t charge,
|
||||
Cache::DeleterFn deleter) {
|
||||
total_key_size += key.size();
|
||||
total_charge += charge;
|
||||
++total_entry_count;
|
||||
// Something slightly more expensive as in stats by category
|
||||
helpers.insert(helper);
|
||||
// Something slightly more expensive as in (future) stats by category
|
||||
deleters.insert(deleter);
|
||||
};
|
||||
if (FLAGS_histograms) {
|
||||
timer.Start();
|
||||
}
|
||||
timer.Start();
|
||||
Cache::ApplyToAllEntriesOptions opts;
|
||||
opts.average_entries_per_lock = FLAGS_gather_stats_entries_per_lock;
|
||||
shared->GetCacheBench()->cache_->ApplyToAllEntries(fn, opts);
|
||||
table_occupancy = shared->GetCacheBench()->cache_->GetOccupancyCount();
|
||||
table_size = shared->GetCacheBench()->cache_->GetTableAddressCount();
|
||||
if (FLAGS_histograms) {
|
||||
stats_hist->Add(timer.ElapsedNanos() / 1000);
|
||||
}
|
||||
stats_hist->Add(timer.ElapsedNanos() / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,8 +528,6 @@ class CacheBench {
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
uint64_t lookup_misses = 0;
|
||||
uint64_t lookup_hits = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
@@ -612,12 +536,18 @@ class CacheBench {
|
||||
StopWatchNano timer(clock);
|
||||
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, FLAGS_skew);
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, max_log_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
Cache::CreateCallback create_cb = [](const void* buf, size_t size,
|
||||
void** out_obj,
|
||||
size_t* charge) -> Status {
|
||||
*out_obj = reinterpret_cast<void*>(new char[size]);
|
||||
memcpy(*out_obj, buf, size);
|
||||
*charge = size;
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
if (FLAGS_histograms) {
|
||||
timer.Start();
|
||||
}
|
||||
timer.Start();
|
||||
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
@@ -625,17 +555,15 @@ class CacheBench {
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
handle = cache_->Lookup(key, &helper2, create_cb, Cache::Priority::LOW,
|
||||
true);
|
||||
if (handle) {
|
||||
++lookup_hits;
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else {
|
||||
++lookup_misses;
|
||||
// do insert
|
||||
Status s = cache_->Insert(key, createValue(thread->rnd), &helper2,
|
||||
FLAGS_value_bytes, &handle);
|
||||
@@ -656,17 +584,14 @@ class CacheBench {
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key, &helper2, /*context*/ nullptr,
|
||||
Cache::Priority::LOW);
|
||||
handle = cache_->Lookup(key, &helper2, create_cb, Cache::Priority::LOW,
|
||||
true);
|
||||
if (handle) {
|
||||
++lookup_hits;
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else {
|
||||
++lookup_misses;
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
@@ -675,14 +600,7 @@ class CacheBench {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
if (FLAGS_histograms) {
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
thread->shared->AddLookupStats(lookup_hits, lookup_misses);
|
||||
}
|
||||
if (FLAGS_early_exit) {
|
||||
MutexLock l(thread->shared->GetMutex());
|
||||
exit(0);
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
@@ -704,9 +622,7 @@ class CacheBench {
|
||||
#ifndef NDEBUG
|
||||
printf("WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
|
||||
#endif
|
||||
printf("----------------------------\n");
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Cache impl name : %s\n", cache_->Name());
|
||||
printf("DMutex impl name : %s\n", DMutex::kName());
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
@@ -1045,14 +961,11 @@ int cache_bench_tool(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (FLAGS_seed == 0) {
|
||||
FLAGS_seed = static_cast<uint32_t>(port::GetProcessID());
|
||||
printf("Using seed = %" PRIu32 "\n", FLAGS_seed);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
|
||||
Vendored
+30
@@ -101,4 +101,34 @@ std::string BlockCacheEntryStatsMapKeys::UsedPercent(CacheEntryRole role) {
|
||||
return GetPrefixedCacheEntryRoleName(kPrefix, role);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct Registry {
|
||||
std::mutex mutex;
|
||||
UnorderedMap<Cache::DeleterFn, CacheEntryRole> role_map;
|
||||
void Register(Cache::DeleterFn fn, CacheEntryRole role) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
role_map[fn] = role;
|
||||
}
|
||||
UnorderedMap<Cache::DeleterFn, CacheEntryRole> Copy() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return role_map;
|
||||
}
|
||||
};
|
||||
|
||||
Registry& GetRegistry() {
|
||||
STATIC_AVOID_DESTRUCTION(Registry, registry);
|
||||
return registry;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RegisterCacheDeleterRole(Cache::DeleterFn fn, CacheEntryRole role) {
|
||||
GetRegistry().Register(fn, role);
|
||||
}
|
||||
|
||||
UnorderedMap<Cache::DeleterFn, CacheEntryRole> CopyCacheDeleterRoleMap() {
|
||||
return GetRegistry().Copy();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+83
@@ -7,8 +7,11 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "util/hash_containers.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -17,4 +20,84 @@ extern std::array<std::string, kNumCacheEntryRoles>
|
||||
extern std::array<std::string, kNumCacheEntryRoles>
|
||||
kCacheEntryRoleToHyphenString;
|
||||
|
||||
// To associate cache entries with their role, we use a hack on the
|
||||
// existing Cache interface. Because the deleter of an entry can authenticate
|
||||
// the code origin of an entry, we can elaborate the choice of deleter to
|
||||
// also encode role information, without inferring false role information
|
||||
// from entries not choosing to encode a role.
|
||||
//
|
||||
// The rest of this file is for handling mappings between deleters and
|
||||
// roles.
|
||||
|
||||
// To infer a role from a deleter, the deleter must be registered. This
|
||||
// can be done "manually" with this function. This function is thread-safe,
|
||||
// and the registration mappings go into private but static storage. (Note
|
||||
// that DeleterFn is a function pointer, not std::function. Registrations
|
||||
// should not be too many.)
|
||||
void RegisterCacheDeleterRole(Cache::DeleterFn fn, CacheEntryRole role);
|
||||
|
||||
// Gets a copy of the registered deleter -> role mappings. This is the only
|
||||
// function for reading the mappings made with RegisterCacheDeleterRole.
|
||||
// Why only this interface for reading?
|
||||
// * This function has to be thread safe, which could incur substantial
|
||||
// overhead. We should not pay this overhead for every deleter look-up.
|
||||
// * This is suitable for preparing for batch operations, like with
|
||||
// CacheEntryStatsCollector.
|
||||
// * The number of mappings should be sufficiently small (dozens).
|
||||
UnorderedMap<Cache::DeleterFn, CacheEntryRole> CopyCacheDeleterRoleMap();
|
||||
|
||||
// ************************************************************** //
|
||||
// An automatic registration infrastructure. This enables code
|
||||
// to simply ask for a deleter associated with a particular type
|
||||
// and role, and registration is automatic. In a sense, this is
|
||||
// a small dependency injection infrastructure, because linking
|
||||
// in new deleter instantiations is essentially sufficient for
|
||||
// making stats collection (using CopyCacheDeleterRoleMap) aware
|
||||
// of them.
|
||||
|
||||
namespace cache_entry_roles_detail {
|
||||
|
||||
template <typename T, CacheEntryRole R>
|
||||
struct RegisteredDeleter {
|
||||
RegisteredDeleter() { RegisterCacheDeleterRole(Delete, R); }
|
||||
|
||||
// These have global linkage to help ensure compiler optimizations do not
|
||||
// break uniqueness for each <T,R>
|
||||
static void Delete(const Slice& /* key */, void* value) {
|
||||
// Supports T == Something[], unlike delete operator
|
||||
std::default_delete<T>()(
|
||||
static_cast<typename std::remove_extent<T>::type*>(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <CacheEntryRole R>
|
||||
struct RegisteredNoopDeleter {
|
||||
RegisteredNoopDeleter() { RegisterCacheDeleterRole(Delete, R); }
|
||||
|
||||
static void Delete(const Slice& /* key */, void* /* value */) {
|
||||
// Here was `assert(value == nullptr);` but we can also put pointers
|
||||
// to static data in Cache, for testing at least.
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cache_entry_roles_detail
|
||||
|
||||
// Get an automatically registered deleter for value type T and role R.
|
||||
// Based on C++ semantics, registration is invoked exactly once in a
|
||||
// thread-safe way on first call to this function, for each <T, R>.
|
||||
template <typename T, CacheEntryRole R>
|
||||
Cache::DeleterFn GetCacheEntryDeleterForRole() {
|
||||
static cache_entry_roles_detail::RegisteredDeleter<T, R> reg;
|
||||
return reg.Delete;
|
||||
}
|
||||
|
||||
// Get an automatically registered no-op deleter (value should be nullptr)
|
||||
// and associated with role R. This is used for Cache "reservation" entries
|
||||
// such as for WriteBufferManager.
|
||||
template <CacheEntryRole R>
|
||||
Cache::DeleterFn GetNoopDeleterForRole() {
|
||||
static cache_entry_roles_detail::RegisteredNoopDeleter<R> reg;
|
||||
return reg.Delete;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+14
-13
@@ -10,8 +10,8 @@
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/typed_cache.h"
|
||||
#include "port/lang.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -111,14 +111,11 @@ class CacheEntryStatsCollector {
|
||||
// Gets or creates a shared instance of CacheEntryStatsCollector in the
|
||||
// cache itself, and saves into `ptr`. This shared_ptr will hold the
|
||||
// entry in cache until all refs are destroyed.
|
||||
static Status GetShared(Cache *raw_cache, SystemClock *clock,
|
||||
static Status GetShared(Cache *cache, SystemClock *clock,
|
||||
std::shared_ptr<CacheEntryStatsCollector> *ptr) {
|
||||
assert(raw_cache);
|
||||
BasicTypedCacheInterface<CacheEntryStatsCollector, CacheEntryRole::kMisc>
|
||||
cache{raw_cache};
|
||||
|
||||
const Slice &cache_key = GetCacheKey();
|
||||
auto h = cache.Lookup(cache_key);
|
||||
|
||||
Cache::Handle *h = cache->Lookup(cache_key);
|
||||
if (h == nullptr) {
|
||||
// Not yet in cache, but Cache doesn't provide a built-in way to
|
||||
// avoid racing insert. So we double-check under a shared mutex,
|
||||
@@ -126,15 +123,15 @@ class CacheEntryStatsCollector {
|
||||
STATIC_AVOID_DESTRUCTION(std::mutex, static_mutex);
|
||||
std::lock_guard<std::mutex> lock(static_mutex);
|
||||
|
||||
h = cache.Lookup(cache_key);
|
||||
h = cache->Lookup(cache_key);
|
||||
if (h == nullptr) {
|
||||
auto new_ptr = new CacheEntryStatsCollector(cache.get(), clock);
|
||||
auto new_ptr = new CacheEntryStatsCollector(cache, clock);
|
||||
// TODO: non-zero charge causes some tests that count block cache
|
||||
// usage to go flaky. Fix the problem somehow so we can use an
|
||||
// accurate charge.
|
||||
size_t charge = 0;
|
||||
Status s =
|
||||
cache.Insert(cache_key, new_ptr, charge, &h, Cache::Priority::HIGH);
|
||||
Status s = cache->Insert(cache_key, new_ptr, charge, Deleter, &h,
|
||||
Cache::Priority::HIGH);
|
||||
if (!s.ok()) {
|
||||
assert(h == nullptr);
|
||||
delete new_ptr;
|
||||
@@ -143,11 +140,11 @@ class CacheEntryStatsCollector {
|
||||
}
|
||||
}
|
||||
// If we reach here, shared entry is in cache with handle `h`.
|
||||
assert(cache.get()->GetCacheItemHelper(h) == cache.GetBasicHelper());
|
||||
assert(cache->GetDeleter(h) == Deleter);
|
||||
|
||||
// Build an aliasing shared_ptr that keeps `ptr` in cache while there
|
||||
// are references.
|
||||
*ptr = cache.SharedGuard(h);
|
||||
*ptr = MakeSharedCacheHandleGuard<CacheEntryStatsCollector>(cache, h);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -160,6 +157,10 @@ class CacheEntryStatsCollector {
|
||||
cache_(cache),
|
||||
clock_(clock) {}
|
||||
|
||||
static void Deleter(const Slice &, void *value) {
|
||||
delete static_cast<CacheEntryStatsCollector *>(value);
|
||||
}
|
||||
|
||||
static const Slice &GetCacheKey() {
|
||||
// For each template instantiation
|
||||
static CacheKey ckey = CacheKey::CreateUniqueForProcessLifetime();
|
||||
|
||||
Vendored
-40
@@ -1,40 +0,0 @@
|
||||
// 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 "cache/cache_helpers.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
void ReleaseCacheHandleCleanup(void* arg1, void* arg2) {
|
||||
Cache* const cache = static_cast<Cache*>(arg1);
|
||||
assert(cache);
|
||||
|
||||
Cache::Handle* const cache_handle = static_cast<Cache::Handle*>(arg2);
|
||||
assert(cache_handle);
|
||||
|
||||
cache->Release(cache_handle);
|
||||
}
|
||||
|
||||
Status WarmInCache(Cache* cache, const Slice& key, const Slice& saved,
|
||||
Cache::CreateContext* create_context,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::Priority priority, size_t* out_charge) {
|
||||
assert(helper);
|
||||
assert(helper->create_cb);
|
||||
Cache::ObjectPtr value;
|
||||
size_t charge;
|
||||
Status st = helper->create_cb(saved, create_context,
|
||||
cache->memory_allocator(), &value, &charge);
|
||||
if (st.ok()) {
|
||||
st =
|
||||
cache->Insert(key, value, helper, charge, /*handle*/ nullptr, priority);
|
||||
if (out_charge) {
|
||||
*out_charge = charge;
|
||||
}
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+21
-15
@@ -7,7 +7,7 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -17,17 +17,22 @@ template <typename T>
|
||||
T* GetFromCacheHandle(Cache* cache, Cache::Handle* handle) {
|
||||
assert(cache);
|
||||
assert(handle);
|
||||
|
||||
return static_cast<T*>(cache->Value(handle));
|
||||
}
|
||||
|
||||
// Simple generic deleter for Cache (to be used with Cache::Insert).
|
||||
template <typename T>
|
||||
void DeleteCacheEntry(const Slice& /* key */, void* value) {
|
||||
delete static_cast<T*>(value);
|
||||
}
|
||||
|
||||
// Turns a T* into a Slice so it can be used as a key with Cache.
|
||||
template <typename T>
|
||||
Slice GetSliceForKey(const T* t) {
|
||||
Slice GetSlice(const T* t) {
|
||||
return Slice(reinterpret_cast<const char*>(t), sizeof(T));
|
||||
}
|
||||
|
||||
void ReleaseCacheHandleCleanup(void* arg1, void* arg2);
|
||||
|
||||
// Generic resource management object for cache handles that releases the handle
|
||||
// when destroyed. Has unique ownership of the handle, so copying it is not
|
||||
// allowed, while moving it transfers ownership.
|
||||
@@ -83,7 +88,7 @@ class CacheHandleGuard {
|
||||
if (cleanable) {
|
||||
if (handle_ != nullptr) {
|
||||
assert(cache_);
|
||||
cleanable->RegisterCleanup(&ReleaseCacheHandleCleanup, cache_, handle_);
|
||||
cleanable->RegisterCleanup(&ReleaseCacheHandle, cache_, handle_);
|
||||
}
|
||||
}
|
||||
ResetFields();
|
||||
@@ -110,6 +115,16 @@ class CacheHandleGuard {
|
||||
value_ = nullptr;
|
||||
}
|
||||
|
||||
static void ReleaseCacheHandle(void* arg1, void* arg2) {
|
||||
Cache* const cache = static_cast<Cache*>(arg1);
|
||||
assert(cache);
|
||||
|
||||
Cache::Handle* const cache_handle = static_cast<Cache::Handle*>(arg2);
|
||||
assert(cache_handle);
|
||||
|
||||
cache->Release(cache_handle);
|
||||
}
|
||||
|
||||
private:
|
||||
Cache* cache_ = nullptr;
|
||||
Cache::Handle* handle_ = nullptr;
|
||||
@@ -124,16 +139,7 @@ template <typename T>
|
||||
std::shared_ptr<T> MakeSharedCacheHandleGuard(Cache* cache,
|
||||
Cache::Handle* handle) {
|
||||
auto wrapper = std::make_shared<CacheHandleGuard<T>>(cache, handle);
|
||||
return std::shared_ptr<T>(wrapper, GetFromCacheHandle<T>(cache, handle));
|
||||
return std::shared_ptr<T>(wrapper, static_cast<T*>(cache->Value(handle)));
|
||||
}
|
||||
|
||||
// Given the persistable data (saved) for a block cache entry, parse that
|
||||
// into a cache entry object and insert it into the given cache. The charge
|
||||
// of the new entry can be returned to the caller through `out_charge`.
|
||||
Status WarmInCache(Cache* cache, const Slice& key, const Slice& saved,
|
||||
Cache::CreateContext* create_context,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::Priority priority = Cache::Priority::LOW,
|
||||
size_t* out_charge = nullptr);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+1
-1
@@ -8,7 +8,7 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
|
||||
Vendored
+9
-8
@@ -13,6 +13,7 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -40,17 +41,17 @@ CacheReservationManagerImpl<
|
||||
template <CacheEntryRole R>
|
||||
CacheReservationManagerImpl<R>::CacheReservationManagerImpl(
|
||||
std::shared_ptr<Cache> cache, bool delayed_decrease)
|
||||
: cache_(cache),
|
||||
delayed_decrease_(delayed_decrease),
|
||||
: delayed_decrease_(delayed_decrease),
|
||||
cache_allocated_size_(0),
|
||||
memory_used_(0) {
|
||||
assert(cache != nullptr);
|
||||
cache_ = cache;
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
CacheReservationManagerImpl<R>::~CacheReservationManagerImpl() {
|
||||
for (auto* handle : dummy_handles_) {
|
||||
cache_.ReleaseAndEraseIfLastRef(handle);
|
||||
cache_->Release(handle, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +115,8 @@ Status CacheReservationManagerImpl<R>::IncreaseCacheReservation(
|
||||
Status return_status = Status::OK();
|
||||
while (new_mem_used > cache_allocated_size_.load(std::memory_order_relaxed)) {
|
||||
Cache::Handle* handle = nullptr;
|
||||
return_status = cache_.Insert(GetNextCacheKey(), kSizeDummyEntry, &handle);
|
||||
return_status = cache_->Insert(GetNextCacheKey(), nullptr, kSizeDummyEntry,
|
||||
GetNoopDeleterForRole<R>(), &handle);
|
||||
|
||||
if (return_status != Status::OK()) {
|
||||
return return_status;
|
||||
@@ -139,7 +141,7 @@ Status CacheReservationManagerImpl<R>::DecreaseCacheReservation(
|
||||
cache_allocated_size_.load(std::memory_order_relaxed)) {
|
||||
assert(!dummy_handles_.empty());
|
||||
auto* handle = dummy_handles_.back();
|
||||
cache_.ReleaseAndEraseIfLastRef(handle);
|
||||
cache_->Release(handle, true);
|
||||
dummy_handles_.pop_back();
|
||||
cache_allocated_size_ -= kSizeDummyEntry;
|
||||
}
|
||||
@@ -167,9 +169,8 @@ Slice CacheReservationManagerImpl<R>::GetNextCacheKey() {
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
const Cache::CacheItemHelper*
|
||||
CacheReservationManagerImpl<R>::TEST_GetCacheItemHelperForRole() {
|
||||
return CacheInterface::GetHelper();
|
||||
Cache::DeleterFn CacheReservationManagerImpl<R>::TEST_GetNoopDeleterForRole() {
|
||||
return GetNoopDeleterForRole<R>();
|
||||
}
|
||||
|
||||
template class CacheReservationManagerImpl<
|
||||
|
||||
Vendored
+4
-5
@@ -18,7 +18,7 @@
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/typed_cache.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/coding.h"
|
||||
@@ -197,10 +197,10 @@ class CacheReservationManagerImpl
|
||||
|
||||
static constexpr std::size_t GetDummyEntrySize() { return kSizeDummyEntry; }
|
||||
|
||||
// For testing only - it is to help ensure the CacheItemHelperForRole<R>
|
||||
// For testing only - it is to help ensure the NoopDeleterForRole<R>
|
||||
// accessed from CacheReservationManagerImpl and the one accessed from the
|
||||
// test are from the same translation units
|
||||
static const Cache::CacheItemHelper *TEST_GetCacheItemHelperForRole();
|
||||
static Cache::DeleterFn TEST_GetNoopDeleterForRole();
|
||||
|
||||
private:
|
||||
static constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
@@ -211,8 +211,7 @@ class CacheReservationManagerImpl
|
||||
Status IncreaseCacheReservation(std::size_t new_mem_used);
|
||||
Status DecreaseCacheReservation(std::size_t new_mem_used);
|
||||
|
||||
using CacheInterface = PlaceholderSharedCacheInterface<R>;
|
||||
CacheInterface cache_;
|
||||
std::shared_ptr<Cache> cache_;
|
||||
bool delayed_decrease_;
|
||||
std::atomic<std::size_t> cache_allocated_size_;
|
||||
std::size_t memory_used_;
|
||||
|
||||
Vendored
+333
-279
File diff suppressed because it is too large.
Load diff
Vendored
+41
-33
@@ -11,57 +11,65 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
ChargedCache::ChargedCache(std::shared_ptr<Cache> cache,
|
||||
std::shared_ptr<Cache> block_cache)
|
||||
: CacheWrapper(cache),
|
||||
: cache_(cache),
|
||||
cache_res_mgr_(std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<
|
||||
CacheReservationManagerImpl<CacheEntryRole::kBlobCache>>(
|
||||
block_cache))) {}
|
||||
|
||||
Status ChargedCache::Insert(const Slice& key, ObjectPtr obj,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
Handle** handle, Priority priority) {
|
||||
Status s = target_->Insert(key, obj, helper, charge, handle, priority);
|
||||
Status ChargedCache::Insert(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter, Handle** handle,
|
||||
Priority priority) {
|
||||
Status s = cache_->Insert(key, value, charge, deleter, handle, priority);
|
||||
if (s.ok()) {
|
||||
// Insert may cause the cache entry eviction if the cache is full. So we
|
||||
// directly call the reservation manager to update the total memory used
|
||||
// in the cache.
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status ChargedCache::Insert(const Slice& key, void* value,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
Handle** handle, Priority priority) {
|
||||
Status s = cache_->Insert(key, value, helper, charge, handle, priority);
|
||||
if (s.ok()) {
|
||||
// Insert may cause the cache entry eviction if the cache is full. So we
|
||||
// directly call the reservation manager to update the total memory used
|
||||
// in the cache.
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Cache::Handle* ChargedCache::Lookup(const Slice& key, Statistics* stats) {
|
||||
return cache_->Lookup(key, stats);
|
||||
}
|
||||
|
||||
Cache::Handle* ChargedCache::Lookup(const Slice& key,
|
||||
const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority, Statistics* stats) {
|
||||
auto handle = target_->Lookup(key, helper, create_context, priority, stats);
|
||||
const CreateCallback& create_cb,
|
||||
Priority priority, bool wait,
|
||||
Statistics* stats) {
|
||||
auto handle = cache_->Lookup(key, helper, create_cb, priority, wait, stats);
|
||||
// Lookup may promote the KV pair from the secondary cache to the primary
|
||||
// cache. So we directly call the reservation manager to update the total
|
||||
// memory used in the cache.
|
||||
if (helper && helper->create_cb) {
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
void ChargedCache::WaitAll(AsyncLookupHandle* async_handles, size_t count) {
|
||||
target_->WaitAll(async_handles, count);
|
||||
// In case of any promotions. Although some could finish by return of
|
||||
// StartAsyncLookup, Wait/WaitAll will generally be used, so simpler to
|
||||
// update here.
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool ChargedCache::Release(Cache::Handle* handle, bool useful,
|
||||
bool erase_if_last_ref) {
|
||||
size_t memory_used_delta = target_->GetUsage(handle);
|
||||
bool erased = target_->Release(handle, useful, erase_if_last_ref);
|
||||
size_t memory_used_delta = cache_->GetUsage(handle);
|
||||
bool erased = cache_->Release(handle, useful, erase_if_last_ref);
|
||||
if (erased) {
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_
|
||||
@@ -72,8 +80,8 @@ bool ChargedCache::Release(Cache::Handle* handle, bool useful,
|
||||
}
|
||||
|
||||
bool ChargedCache::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
size_t memory_used_delta = target_->GetUsage(handle);
|
||||
bool erased = target_->Release(handle, erase_if_last_ref);
|
||||
size_t memory_used_delta = cache_->GetUsage(handle);
|
||||
bool erased = cache_->Release(handle, erase_if_last_ref);
|
||||
if (erased) {
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_
|
||||
@@ -84,25 +92,25 @@ bool ChargedCache::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
}
|
||||
|
||||
void ChargedCache::Erase(const Slice& key) {
|
||||
target_->Erase(key);
|
||||
cache_->Erase(key);
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void ChargedCache::EraseUnRefEntries() {
|
||||
target_->EraseUnRefEntries();
|
||||
cache_->EraseUnRefEntries();
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void ChargedCache::SetCapacity(size_t capacity) {
|
||||
target_->SetCapacity(capacity);
|
||||
cache_->SetCapacity(capacity);
|
||||
// SetCapacity can result in evictions when the cache capacity is decreased,
|
||||
// so we would want to update the cache reservation here as well.
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(target_->GetUsage())
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
|
||||
Vendored
+71
-9
@@ -8,7 +8,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -17,21 +17,22 @@ class ConcurrentCacheReservationManager;
|
||||
// A cache interface which wraps around another cache and takes care of
|
||||
// reserving space in block cache towards a single global memory limit, and
|
||||
// forwards all the calls to the underlying cache.
|
||||
class ChargedCache : public CacheWrapper {
|
||||
class ChargedCache : public Cache {
|
||||
public:
|
||||
ChargedCache(std::shared_ptr<Cache> cache,
|
||||
std::shared_ptr<Cache> block_cache);
|
||||
~ChargedCache() override = default;
|
||||
|
||||
Status Insert(const Slice& key, ObjectPtr obj, const CacheItemHelper* helper,
|
||||
Status Insert(const Slice& key, void* value, size_t charge, DeleterFn deleter,
|
||||
Handle** handle, Priority priority) override;
|
||||
Status Insert(const Slice& key, void* value, const CacheItemHelper* helper,
|
||||
size_t charge, Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override;
|
||||
|
||||
Cache::Handle* Lookup(const Slice& key, Statistics* stats) override;
|
||||
Cache::Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override;
|
||||
|
||||
void WaitAll(AsyncLookupHandle* async_handles, size_t count) override;
|
||||
const CreateCallback& create_cb, Priority priority,
|
||||
bool wait, Statistics* stats = nullptr) override;
|
||||
|
||||
bool Release(Cache::Handle* handle, bool useful,
|
||||
bool erase_if_last_ref = false) override;
|
||||
@@ -43,9 +44,69 @@ class ChargedCache : public CacheWrapper {
|
||||
static const char* kClassName() { return "ChargedCache"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
uint64_t NewId() override { return cache_->NewId(); }
|
||||
|
||||
void SetCapacity(size_t capacity) override;
|
||||
|
||||
inline Cache* GetCache() const { return target_.get(); }
|
||||
void SetStrictCapacityLimit(bool strict_capacity_limit) override {
|
||||
cache_->SetStrictCapacityLimit(strict_capacity_limit);
|
||||
}
|
||||
|
||||
bool HasStrictCapacityLimit() const override {
|
||||
return cache_->HasStrictCapacityLimit();
|
||||
}
|
||||
|
||||
void* Value(Cache::Handle* handle) override { return cache_->Value(handle); }
|
||||
|
||||
bool IsReady(Cache::Handle* handle) override {
|
||||
return cache_->IsReady(handle);
|
||||
}
|
||||
|
||||
void Wait(Cache::Handle* handle) override { cache_->Wait(handle); }
|
||||
|
||||
void WaitAll(std::vector<Handle*>& handles) override {
|
||||
cache_->WaitAll(handles);
|
||||
}
|
||||
|
||||
bool Ref(Cache::Handle* handle) override { return cache_->Ref(handle); }
|
||||
|
||||
size_t GetCapacity() const override { return cache_->GetCapacity(); }
|
||||
|
||||
size_t GetUsage() const override { return cache_->GetUsage(); }
|
||||
|
||||
size_t GetUsage(Cache::Handle* handle) const override {
|
||||
return cache_->GetUsage(handle);
|
||||
}
|
||||
|
||||
size_t GetPinnedUsage() const override { return cache_->GetPinnedUsage(); }
|
||||
|
||||
size_t GetCharge(Cache::Handle* handle) const override {
|
||||
return cache_->GetCharge(handle);
|
||||
}
|
||||
|
||||
Cache::DeleterFn GetDeleter(Cache::Handle* handle) const override {
|
||||
return cache_->GetDeleter(handle);
|
||||
}
|
||||
|
||||
void ApplyToAllEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
Cache::DeleterFn deleter)>& callback,
|
||||
const Cache::ApplyToAllEntriesOptions& opts) override {
|
||||
cache_->ApplyToAllEntries(callback, opts);
|
||||
}
|
||||
|
||||
void ApplyToAllCacheEntries(void (*callback)(void* value, size_t charge),
|
||||
bool thread_safe) override {
|
||||
cache_->ApplyToAllCacheEntries(callback, thread_safe);
|
||||
}
|
||||
|
||||
std::string GetPrintableOptions() const override {
|
||||
return cache_->GetPrintableOptions();
|
||||
}
|
||||
|
||||
void DisownData() override { return cache_->DisownData(); }
|
||||
|
||||
inline Cache* GetCache() const { return cache_.get(); }
|
||||
|
||||
inline ConcurrentCacheReservationManager* TEST_GetCacheReservationManager()
|
||||
const {
|
||||
@@ -53,6 +114,7 @@ class ChargedCache : public CacheWrapper {
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
|
||||
};
|
||||
|
||||
|
||||
Vendored
+630
-1088
File diff suppressed because it is too large.
Load diff
Vendored
+174
-344
@@ -24,18 +24,15 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/math.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace clock_cache {
|
||||
namespace hyper_clock_cache {
|
||||
|
||||
// Forward declaration of friend class.
|
||||
template <class ClockCache>
|
||||
class ClockCacheTest;
|
||||
|
||||
// HyperClockCache is an alternative to LRUCache specifically tailored for
|
||||
// use as BlockBasedTableOptions::block_cache
|
||||
// HyperClockCache is an experimental alternative to LRUCache.
|
||||
//
|
||||
// Benefits
|
||||
// --------
|
||||
@@ -147,7 +144,7 @@ class ClockCacheTest;
|
||||
// (erased by user) but can be read by existing references, and ref count
|
||||
// changed by Ref and Release.
|
||||
//
|
||||
// A special case is "standalone" entries, which are heap-allocated handles
|
||||
// A special case is "detached" entries, which are heap-allocated handles
|
||||
// not in the table. They are always Invisible and freed on zero refs.
|
||||
//
|
||||
// State transitions:
|
||||
@@ -202,8 +199,8 @@ class ClockCacheTest;
|
||||
// table occupancy limit has been reached. If strict_capacity_limit=false,
|
||||
// we must never fail Insert, and if a Handle* is provided, we have to return
|
||||
// a usable Cache handle on success. The solution to this (typically rare)
|
||||
// problem is "standalone" handles, which are usable by the caller but not
|
||||
// actually available for Lookup in the Cache. Standalone handles are allocated
|
||||
// problem is "detached" handles, which are usable by the caller but not
|
||||
// actually available for Lookup in the Cache. Detached handles are allocated
|
||||
// independently on the heap and specially marked so that they are freed on
|
||||
// the heap when their last reference is released.
|
||||
//
|
||||
@@ -283,29 +280,52 @@ class ClockCacheTest;
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
// The load factor p is a real number in (0, 1) such that at all
|
||||
// times at most a fraction p of all slots, without counting tombstones,
|
||||
// are occupied by elements. This means that the probability that a random
|
||||
// probe hits an occupied slot is at most p, and thus at most 1/p probes
|
||||
// are required on average. For example, p = 70% implies that between 1 and 2
|
||||
// probes are needed on average (bear in mind that this reasoning doesn't
|
||||
// consider the effects of clustering over time, which should be negligible
|
||||
// with double hashing).
|
||||
// Because the size of the hash table is always rounded up to the next
|
||||
// power of 2, p is really an upper bound on the actual load factor---the
|
||||
// actual load factor is anywhere between p/2 and p. This is a bit wasteful,
|
||||
// but bear in mind that slots only hold metadata, not actual values.
|
||||
// Since space cost is dominated by the values (the LSM blocks),
|
||||
// overprovisioning the table with metadata only increases the total cache space
|
||||
// usage by a tiny fraction.
|
||||
constexpr double kLoadFactor = 0.7;
|
||||
|
||||
// The user can exceed kLoadFactor if the sizes of the inserted values don't
|
||||
// match estimated_value_size, or in some rare cases with
|
||||
// strict_capacity_limit == false. To avoid degenerate performance, we set a
|
||||
// strict upper bound on the load factor.
|
||||
constexpr double kStrictLoadFactor = 0.84;
|
||||
|
||||
struct ClockHandleBasicData {
|
||||
Cache::ObjectPtr value = nullptr;
|
||||
const Cache::CacheItemHelper* helper = nullptr;
|
||||
void* value = nullptr;
|
||||
Cache::DeleterFn deleter = nullptr;
|
||||
// A lossless, reversible hash of the fixed-size (16 byte) cache key. This
|
||||
// eliminates the need to store a hash separately.
|
||||
UniqueId64x2 hashed_key = kNullUniqueId64x2;
|
||||
size_t total_charge = 0;
|
||||
|
||||
inline size_t GetTotalCharge() const { return total_charge; }
|
||||
|
||||
// Calls deleter (if non-null) on cache key and value
|
||||
void FreeData(MemoryAllocator* allocator) const;
|
||||
void FreeData() const;
|
||||
|
||||
// Required by concept HandleImpl
|
||||
const UniqueId64x2& GetHash() const { return hashed_key; }
|
||||
};
|
||||
|
||||
struct ClockHandle : public ClockHandleBasicData {
|
||||
// Target size to be exactly a common cache line size (see static_assert in
|
||||
// clock_cache.cc)
|
||||
struct ALIGN_AS(64U) ClockHandle : public ClockHandleBasicData {
|
||||
// Constants for handling the atomic `meta` word, which tracks most of the
|
||||
// state of the handle. The meta word looks like this:
|
||||
// low bits high bits
|
||||
// -----------------------------------------------------------------------
|
||||
// | acquire counter | release counter | hit bit | state marker |
|
||||
// | acquire counter | release counter | state marker |
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// For reading or updating counters in meta word.
|
||||
@@ -319,13 +339,8 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
static constexpr uint64_t kReleaseIncrement = uint64_t{1}
|
||||
<< kReleaseCounterShift;
|
||||
|
||||
// For setting the hit bit
|
||||
static constexpr uint8_t kHitBitShift = 2U * kCounterNumBits;
|
||||
static constexpr uint64_t kHitBitMask = uint64_t{1} << kHitBitShift;
|
||||
;
|
||||
|
||||
// For reading or updating the state marker in meta word
|
||||
static constexpr uint8_t kStateShift = kHitBitShift + 1;
|
||||
static constexpr uint8_t kStateShift = 2U * kCounterNumBits;
|
||||
|
||||
// Bits contribution to state marker.
|
||||
// Occupied means any state other than empty
|
||||
@@ -355,261 +370,91 @@ struct ClockHandle : public ClockHandleBasicData {
|
||||
static constexpr uint8_t kMaxCountdown = kHighCountdown;
|
||||
// TODO: make these coundown values tuning parameters for eviction?
|
||||
|
||||
// See above. Mutable for read reference counting.
|
||||
mutable std::atomic<uint64_t> meta{};
|
||||
// See above
|
||||
std::atomic<uint64_t> meta{};
|
||||
// The number of elements that hash to this slot or a lower one, but wind
|
||||
// up in this slot or a higher one.
|
||||
std::atomic<uint32_t> displacements{};
|
||||
|
||||
// True iff the handle is allocated separately from hash table.
|
||||
bool detached = false;
|
||||
}; // struct ClockHandle
|
||||
|
||||
class BaseClockTable {
|
||||
class ClockHandleTable {
|
||||
public:
|
||||
BaseClockTable(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) {}
|
||||
explicit ClockHandleTable(int hash_bits, bool initial_charge_metadata);
|
||||
~ClockHandleTable();
|
||||
|
||||
template <class Table>
|
||||
typename Table::HandleImpl* CreateStandalone(ClockHandleBasicData& proto,
|
||||
size_t capacity,
|
||||
bool strict_capacity_limit,
|
||||
bool allow_uncharged);
|
||||
Status Insert(const ClockHandleBasicData& proto, ClockHandle** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
bool strict_capacity_limit);
|
||||
|
||||
template <class Table>
|
||||
Status Insert(const ClockHandleBasicData& proto,
|
||||
typename Table::HandleImpl** handle, Cache::Priority priority,
|
||||
size_t capacity, bool strict_capacity_limit);
|
||||
ClockHandle* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
bool Release(ClockHandle* handle, bool useful, bool erase_if_last_ref);
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
void Erase(const UniqueId64x2& hashed_key);
|
||||
|
||||
void ConstApplyToEntriesRange(std::function<void(const ClockHandle&)> func,
|
||||
size_t index_begin, size_t index_end,
|
||||
bool apply_if_will_be_deleted) const;
|
||||
|
||||
void EraseUnRefEntries();
|
||||
|
||||
size_t GetTableSize() const { return size_t{1} << length_bits_; }
|
||||
|
||||
int GetLengthBits() const { return length_bits_; }
|
||||
|
||||
size_t GetOccupancyLimit() const { return occupancy_limit_; }
|
||||
|
||||
size_t GetOccupancy() const {
|
||||
return occupancy_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t GetStandaloneUsage() const {
|
||||
return standalone_usage_.load(std::memory_order_relaxed);
|
||||
size_t GetDetachedUsage() const {
|
||||
return detached_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
uint32_t GetHashSeed() const { return hash_seed_; }
|
||||
|
||||
struct EvictionData {
|
||||
size_t freed_charge = 0;
|
||||
size_t freed_count = 0;
|
||||
};
|
||||
|
||||
void TrackAndReleaseEvictedEntry(ClockHandle* h, EvictionData* data);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Acquire N references
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(ClockHandle& handle, size_t n);
|
||||
// Helper for TEST_ReleaseN
|
||||
void TEST_ReleaseNMinus1(ClockHandle* handle, size_t n);
|
||||
#endif
|
||||
|
||||
private: // fns
|
||||
// Creates a "standalone" handle for returning from an Insert operation that
|
||||
// cannot be completed by actually inserting into the table.
|
||||
// Updates `standalone_usage_` but not `usage_` nor `occupancy_`.
|
||||
template <class HandleImpl>
|
||||
HandleImpl* StandaloneInsert(const ClockHandleBasicData& proto);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=true rules. This
|
||||
// means the operation might fail with Status::MemoryLimit. If
|
||||
// `need_evict_for_occupancy`, then eviction of at least one entry is
|
||||
// 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,
|
||||
bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
// Helper for updating `usage_` for new entry with given `total_charge`
|
||||
// and evicting if needed under strict_capacity_limit=false rules. This
|
||||
// means that updating `usage_` always succeeds even if forced to exceed
|
||||
// capacity. If `need_evict_for_occupancy`, then eviction of at least one
|
||||
// entry is required, and the operation should return false if such eviction
|
||||
// is not possible. `usage_` is not updated in that case. Otherwise, returns
|
||||
// true, indicating success.
|
||||
// NOTE: occupancy_ is not managed in this function
|
||||
template <class Table>
|
||||
bool ChargeUsageMaybeEvictNonStrict(size_t total_charge, size_t capacity,
|
||||
bool need_evict_for_occupancy,
|
||||
typename Table::InsertState& state);
|
||||
|
||||
protected: // data
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
|
||||
// TODO: is this separation needed if we don't do background evictions?
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including standalone)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by standalone entries (not in table)
|
||||
std::atomic<size_t> standalone_usage_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
const CacheMetadataChargePolicy metadata_charge_policy_;
|
||||
|
||||
// From Cache, for deleter
|
||||
MemoryAllocator* const allocator_;
|
||||
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
|
||||
// A reference to ShardedCacheBase::hash_seed_
|
||||
const uint32_t& hash_seed_;
|
||||
};
|
||||
|
||||
class FixedHyperClockTable : public BaseClockTable {
|
||||
public:
|
||||
// Target size to be exactly a common cache line size (see static_assert in
|
||||
// clock_cache.cc)
|
||||
struct ALIGN_AS(64U) HandleImpl : public ClockHandle {
|
||||
// The number of elements that hash to this slot or a lower one, but wind
|
||||
// up in this slot or a higher one.
|
||||
std::atomic<uint32_t> displacements{};
|
||||
|
||||
// Whether this is a "deteched" handle that is independently allocated
|
||||
// with `new` (so must be deleted with `delete`).
|
||||
// TODO: ideally this would be packed into some other data field, such
|
||||
// as upper bits of total_charge, but that incurs a measurable performance
|
||||
// regression.
|
||||
bool standalone = false;
|
||||
|
||||
inline bool IsStandalone() const { return standalone; }
|
||||
|
||||
inline void SetStandalone() { standalone = true; }
|
||||
}; // struct HandleImpl
|
||||
|
||||
struct Opts {
|
||||
explicit Opts(size_t _estimated_value_size)
|
||||
: estimated_value_size(_estimated_value_size) {}
|
||||
explicit Opts(const HyperClockCacheOptions& opts) {
|
||||
assert(opts.estimated_entry_charge > 0);
|
||||
estimated_value_size = opts.estimated_entry_charge;
|
||||
}
|
||||
size_t estimated_value_size;
|
||||
};
|
||||
|
||||
FixedHyperClockTable(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);
|
||||
~FixedHyperClockTable();
|
||||
|
||||
// For BaseClockTable::Insert
|
||||
struct InsertState {};
|
||||
|
||||
void StartInsert(InsertState& state);
|
||||
|
||||
// Returns true iff there is room for the proposed number of entries.
|
||||
bool GrowIfNeeded(size_t new_occupancy, InsertState& state);
|
||||
|
||||
HandleImpl* DoInsert(const ClockHandleBasicData& proto,
|
||||
uint64_t initial_countdown, bool take_ref,
|
||||
InsertState& state);
|
||||
|
||||
// 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);
|
||||
|
||||
HandleImpl* Lookup(const UniqueId64x2& hashed_key);
|
||||
|
||||
bool Release(HandleImpl* handle, bool useful, bool erase_if_last_ref);
|
||||
|
||||
void Erase(const UniqueId64x2& hashed_key);
|
||||
|
||||
void EraseUnRefEntries();
|
||||
|
||||
size_t GetTableSize() const { return size_t{1} << length_bits_; }
|
||||
|
||||
size_t GetOccupancyLimit() const { return occupancy_limit_; }
|
||||
|
||||
const HandleImpl* HandlePtr(size_t idx) const { return &array_[idx]; }
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t& TEST_MutableOccupancyLimit() {
|
||||
return const_cast<size_t&>(occupancy_limit_);
|
||||
}
|
||||
|
||||
// Release N references
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
#endif
|
||||
|
||||
// The load factor p is a real number in (0, 1) such that at all
|
||||
// times at most a fraction p of all slots, without counting tombstones,
|
||||
// are occupied by elements. This means that the probability that a random
|
||||
// probe hits an occupied slot is at most p, and thus at most 1/p probes
|
||||
// are required on average. For example, p = 70% implies that between 1 and 2
|
||||
// probes are needed on average (bear in mind that this reasoning doesn't
|
||||
// consider the effects of clustering over time, which should be negligible
|
||||
// with double hashing).
|
||||
// Because the size of the hash table is always rounded up to the next
|
||||
// power of 2, p is really an upper bound on the actual load factor---the
|
||||
// actual load factor is anywhere between p/2 and p. This is a bit wasteful,
|
||||
// but bear in mind that slots only hold metadata, not actual values.
|
||||
// Since space cost is dominated by the values (the LSM blocks),
|
||||
// overprovisioning the table with metadata only increases the total cache
|
||||
// space usage by a tiny fraction.
|
||||
static constexpr double kLoadFactor = 0.7;
|
||||
|
||||
// The user can exceed kLoadFactor if the sizes of the inserted values don't
|
||||
// match estimated_value_size, or in some rare cases with
|
||||
// strict_capacity_limit == false. To avoid degenerate performance, we set a
|
||||
// strict upper bound on the load factor.
|
||||
static constexpr double kStrictLoadFactor = 0.84;
|
||||
void TEST_ReleaseN(ClockHandle* handle, size_t n);
|
||||
|
||||
private: // functions
|
||||
// Returns x mod 2^{length_bits_}.
|
||||
inline size_t ModTableSize(uint64_t x) {
|
||||
return BitwiseAnd(x, length_bits_mask_);
|
||||
return static_cast<size_t>(x) & length_bits_mask_;
|
||||
}
|
||||
|
||||
// Returns the first slot in the probe sequence with a handle e such that
|
||||
// match_fn(e) is true. At every step, the function first tests whether
|
||||
// match_fn(e) holds. If this is false, it evaluates abort_fn(e) to decide
|
||||
// whether the search should be aborted, and if so, FindSlot immediately
|
||||
// returns nullptr. For every handle e that is not a match and not aborted,
|
||||
// FindSlot runs update_fn(e, is_last) where is_last is set to true iff that
|
||||
// slot will be the last probed because the next would cycle back to the first
|
||||
// slot probed. This function uses templates instead of std::function to
|
||||
// minimize the risk of heap-allocated closures being created.
|
||||
template <typename MatchFn, typename AbortFn, typename UpdateFn>
|
||||
inline HandleImpl* FindSlot(const UniqueId64x2& hashed_key,
|
||||
const MatchFn& match_fn, const AbortFn& abort_fn,
|
||||
const UpdateFn& update_fn);
|
||||
// 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, size_t* freed_charge,
|
||||
size_t* freed_count);
|
||||
|
||||
// Returns the first slot in the probe sequence, starting from the given
|
||||
// probe number, with a handle e such that match(e) is true. At every
|
||||
// step, the function first tests whether match(e) holds. If this is false,
|
||||
// it evaluates abort(e) to decide whether the search should be aborted,
|
||||
// and in the affirmative returns -1. For every handle e probed except
|
||||
// the last one, the function runs update(e).
|
||||
// The probe parameter is modified as follows. We say a probe to a handle
|
||||
// e is aborting if match(e) is false and abort(e) is true. Then the final
|
||||
// value of probe is one more than the last non-aborting probe during the
|
||||
// call. This is so that that the variable can be used to keep track of
|
||||
// progress across consecutive calls to FindSlot.
|
||||
inline ClockHandle* FindSlot(const UniqueId64x2& hashed_key,
|
||||
std::function<bool(ClockHandle*)> match,
|
||||
std::function<bool(ClockHandle*)> stop,
|
||||
std::function<void(ClockHandle*)> update,
|
||||
size_t& probe);
|
||||
|
||||
// Re-decrement all displacements in probe path starting from beginning
|
||||
// until (not including) the given handle
|
||||
inline void Rollback(const UniqueId64x2& hashed_key, const HandleImpl* h);
|
||||
|
||||
// Subtracts `total_charge` from `usage_` and 1 from `occupancy_`.
|
||||
// Ideally this comes after releasing the entry itself so that we
|
||||
// actually have the available occupancy/usage that is claimed.
|
||||
// However, that means total_charge has to be saved from the handle
|
||||
// before releasing it so that it can be provided to this function.
|
||||
inline void ReclaimEntryUsage(size_t total_charge);
|
||||
|
||||
MemoryAllocator* GetAllocator() const { return allocator_; }
|
||||
|
||||
// Returns the number of bits used to hash an element in the hash
|
||||
// table.
|
||||
static int CalcHashBits(size_t capacity, size_t estimated_value_size,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
void Rollback(const UniqueId64x2& hashed_key, const ClockHandle* h);
|
||||
|
||||
private: // data
|
||||
// Number of hash bits used for table index.
|
||||
@@ -623,52 +468,58 @@ class FixedHyperClockTable : public BaseClockTable {
|
||||
const size_t occupancy_limit_;
|
||||
|
||||
// Array of slots comprising the hash table.
|
||||
const std::unique_ptr<HandleImpl[]> array_;
|
||||
}; // class FixedHyperClockTable
|
||||
const std::unique_ptr<ClockHandle[]> array_;
|
||||
|
||||
// Placeholder for future automatic table variant
|
||||
// For now, just use FixedHyperClockTable.
|
||||
class AutoHyperClockTable : public FixedHyperClockTable {
|
||||
public:
|
||||
using FixedHyperClockTable::FixedHyperClockTable;
|
||||
}; // class AutoHyperClockTable
|
||||
// We partition the following members into different cache lines
|
||||
// to avoid false sharing among Lookup, Release, Erase and Insert
|
||||
// operations in ClockCacheShard.
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Clock algorithm sweep pointer.
|
||||
std::atomic<uint64_t> clock_pointer_{};
|
||||
|
||||
ALIGN_AS(CACHE_LINE_SIZE)
|
||||
// Number of elements in the table.
|
||||
std::atomic<size_t> occupancy_{};
|
||||
|
||||
// Memory usage by entries tracked by the cache (including detached)
|
||||
std::atomic<size_t> usage_{};
|
||||
|
||||
// Part of usage by detached entries (not in table)
|
||||
std::atomic<size_t> detached_usage_{};
|
||||
}; // class ClockHandleTable
|
||||
|
||||
// A single shard of sharded cache.
|
||||
template <class TableT>
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
public:
|
||||
using Table = TableT;
|
||||
ClockCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback,
|
||||
const uint32_t* hash_seed, const typename Table::Opts& opts);
|
||||
ClockCacheShard(size_t capacity, size_t estimated_value_size,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
|
||||
// For CacheShard concept
|
||||
using HandleImpl = typename Table::HandleImpl;
|
||||
using HandleImpl = ClockHandle;
|
||||
// Hash is lossless hash of 128-bit key
|
||||
using HashVal = UniqueId64x2;
|
||||
using HashCref = const HashVal&;
|
||||
static inline uint32_t HashPieceForSharding(HashCref hash) {
|
||||
return Upper32of64(hash[0]);
|
||||
}
|
||||
static inline HashVal ComputeHash(const Slice& key, uint32_t seed) {
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
assert(key.size() == kCacheKeySize);
|
||||
HashVal in;
|
||||
HashVal out;
|
||||
// NOTE: endian dependence
|
||||
// TODO: use GetUnaligned?
|
||||
std::memcpy(&in, key.data(), kCacheKeySize);
|
||||
BijectiveHash2x64(in[1], in[0] ^ seed, &out[1], &out[0]);
|
||||
BijectiveHash2x64(in[1], in[0], &out[1], &out[0]);
|
||||
return out;
|
||||
}
|
||||
|
||||
// For reconstructing key from hashed_key. Requires the caller to provide
|
||||
// backing storage for the Slice in `unhashed`
|
||||
static inline Slice ReverseHash(const UniqueId64x2& hashed,
|
||||
UniqueId64x2* unhashed, uint32_t seed) {
|
||||
UniqueId64x2* unhashed) {
|
||||
BijectiveUnhash2x64(hashed[1], hashed[0], &(*unhashed)[1], &(*unhashed)[0]);
|
||||
(*unhashed)[0] ^= seed;
|
||||
// NOTE: endian dependence
|
||||
return Slice(reinterpret_cast<const char*>(unhashed), kCacheKeySize);
|
||||
}
|
||||
@@ -680,70 +531,74 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
|
||||
void SetStrictCapacityLimit(bool strict_capacity_limit);
|
||||
|
||||
Status Insert(const Slice& key, const UniqueId64x2& hashed_key,
|
||||
Cache::ObjectPtr value, const Cache::CacheItemHelper* helper,
|
||||
size_t charge, HandleImpl** handle, Cache::Priority priority);
|
||||
Status Insert(const Slice& key, const UniqueId64x2& hashed_key, void* value,
|
||||
size_t charge, Cache::DeleterFn deleter, ClockHandle** handle,
|
||||
Cache::Priority priority);
|
||||
|
||||
HandleImpl* CreateStandalone(const Slice& key, const UniqueId64x2& hashed_key,
|
||||
Cache::ObjectPtr obj,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, bool allow_uncharged);
|
||||
ClockHandle* Lookup(const Slice& key, const UniqueId64x2& hashed_key);
|
||||
|
||||
HandleImpl* Lookup(const Slice& key, const UniqueId64x2& hashed_key);
|
||||
bool Release(ClockHandle* handle, bool useful, bool erase_if_last_ref);
|
||||
|
||||
bool Release(HandleImpl* handle, bool useful, bool erase_if_last_ref);
|
||||
bool Release(ClockHandle* handle, bool erase_if_last_ref = false);
|
||||
|
||||
bool Release(HandleImpl* handle, bool erase_if_last_ref = false);
|
||||
|
||||
bool Ref(HandleImpl* handle);
|
||||
bool Ref(ClockHandle* handle);
|
||||
|
||||
void Erase(const Slice& key, const UniqueId64x2& hashed_key);
|
||||
|
||||
size_t GetCapacity() const;
|
||||
|
||||
size_t GetUsage() const;
|
||||
|
||||
size_t GetStandaloneUsage() const;
|
||||
|
||||
size_t GetPinnedUsage() const;
|
||||
|
||||
size_t GetOccupancyCount() const;
|
||||
|
||||
size_t GetOccupancyLimit() const;
|
||||
|
||||
size_t GetTableAddressCount() const;
|
||||
|
||||
void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, Cache::ObjectPtr obj,
|
||||
size_t charge,
|
||||
const Cache::CacheItemHelper* helper)>& callback,
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
size_t average_entries_per_lock, size_t* state);
|
||||
|
||||
void EraseUnRefEntries();
|
||||
|
||||
std::string GetPrintableOptions() const { return std::string{}; }
|
||||
|
||||
HandleImpl* Lookup(const Slice& key, const UniqueId64x2& hashed_key,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
Cache::CreateContext* /*create_context*/,
|
||||
Cache::Priority /*priority*/, Statistics* /*stats*/) {
|
||||
// SecondaryCache not yet supported
|
||||
Status Insert(const Slice& key, const UniqueId64x2& hashed_key, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
ClockHandle** handle, Cache::Priority priority) {
|
||||
return Insert(key, hashed_key, value, charge, helper->del_cb, handle,
|
||||
priority);
|
||||
}
|
||||
|
||||
ClockHandle* Lookup(const Slice& key, const UniqueId64x2& hashed_key,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
const Cache::CreateCallback& /*create_cb*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/,
|
||||
Statistics* /*stats*/) {
|
||||
return Lookup(key, hashed_key);
|
||||
}
|
||||
|
||||
Table& GetTable() { return table_; }
|
||||
const Table& GetTable() const { return table_; }
|
||||
bool IsReady(ClockHandle* /*handle*/) { return true; }
|
||||
|
||||
void Wait(ClockHandle* /*handle*/) {}
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t& TEST_MutableOccupancyLimit() {
|
||||
return table_.TEST_MutableOccupancyLimit();
|
||||
}
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(HandleImpl* handle, size_t n);
|
||||
void TEST_ReleaseN(HandleImpl* handle, size_t n);
|
||||
#endif
|
||||
void TEST_RefN(ClockHandle* handle, size_t n);
|
||||
void TEST_ReleaseN(ClockHandle* handle, size_t n);
|
||||
|
||||
private: // functions
|
||||
friend class ClockCache;
|
||||
friend class ClockCacheTest;
|
||||
|
||||
ClockHandle* DetachedInsert(const ClockHandleBasicData& h);
|
||||
|
||||
// Returns the number of bits used to hash an element in the hash
|
||||
// table.
|
||||
static int CalcHashBits(size_t capacity, size_t estimated_value_size,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
|
||||
private: // data
|
||||
Table table_;
|
||||
ClockHandleTable table_;
|
||||
|
||||
// Maximum total charge of all elements stored in the table.
|
||||
std::atomic<size_t> capacity_;
|
||||
@@ -752,51 +607,26 @@ class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShardBase {
|
||||
std::atomic<bool> strict_capacity_limit_;
|
||||
}; // class ClockCacheShard
|
||||
|
||||
template <class Table>
|
||||
class BaseHyperClockCache : public ShardedCache<ClockCacheShard<Table>> {
|
||||
class HyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public ShardedCache<ClockCacheShard> {
|
||||
public:
|
||||
using Shard = ClockCacheShard<Table>;
|
||||
using Handle = Cache::Handle;
|
||||
using CacheItemHelper = Cache::CacheItemHelper;
|
||||
HyperClockCache(size_t capacity, size_t estimated_value_size,
|
||||
int num_shard_bits, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator);
|
||||
|
||||
explicit BaseHyperClockCache(const HyperClockCacheOptions& opts);
|
||||
const char* Name() const override { return "HyperClockCache"; }
|
||||
|
||||
Cache::ObjectPtr Value(Handle* handle) override;
|
||||
void* Value(Handle* handle) override;
|
||||
|
||||
size_t GetCharge(Handle* handle) const override;
|
||||
|
||||
const CacheItemHelper* GetCacheItemHelper(Handle* handle) const override;
|
||||
DeleterFn GetDeleter(Handle* handle) const override;
|
||||
}; // class HyperClockCache
|
||||
|
||||
void ReportProblems(
|
||||
const std::shared_ptr<Logger>& /*info_log*/) const override;
|
||||
};
|
||||
|
||||
class FixedHyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public BaseHyperClockCache<FixedHyperClockTable> {
|
||||
public:
|
||||
using BaseHyperClockCache::BaseHyperClockCache;
|
||||
|
||||
const char* Name() const override { return "FixedHyperClockCache"; }
|
||||
|
||||
void ReportProblems(
|
||||
const std::shared_ptr<Logger>& /*info_log*/) const override;
|
||||
}; // class FixedHyperClockCache
|
||||
|
||||
// Placeholder for future automatic HCC variant
|
||||
class AutoHyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public BaseHyperClockCache<AutoHyperClockTable> {
|
||||
public:
|
||||
using BaseHyperClockCache::BaseHyperClockCache;
|
||||
|
||||
const char* Name() const override { return "AutoHyperClockCache"; }
|
||||
}; // class AutoHyperClockCache
|
||||
|
||||
} // namespace clock_cache
|
||||
} // namespace hyper_clock_cache
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+82
-89
@@ -9,7 +9,7 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -17,29 +17,30 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
CompressedSecondaryCache::CompressedSecondaryCache(
|
||||
const CompressedSecondaryCacheOptions& opts)
|
||||
: cache_(opts.LRUCacheOptions::MakeSharedCache()),
|
||||
cache_options_(opts),
|
||||
cache_res_mgr_(std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache_))),
|
||||
disable_cache_(opts.capacity == 0) {}
|
||||
|
||||
CompressedSecondaryCache::~CompressedSecondaryCache() {
|
||||
assert(cache_res_mgr_->GetTotalReservedCacheSize() == 0);
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
CompressionType compression_type, uint32_t compress_format_version,
|
||||
bool enable_custom_split_merge)
|
||||
: cache_options_(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, low_pri_pool_ratio, memory_allocator,
|
||||
use_adaptive_mutex, metadata_charge_policy,
|
||||
compression_type, compress_format_version,
|
||||
enable_custom_split_merge) {
|
||||
cache_ =
|
||||
NewLRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, low_pri_pool_ratio);
|
||||
}
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
bool& kept_in_sec_cache) {
|
||||
assert(helper);
|
||||
if (disable_cache_) {
|
||||
return nullptr;
|
||||
}
|
||||
CompressedSecondaryCache::~CompressedSecondaryCache() { cache_.reset(); }
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
const Slice& key, const Cache::CreateCallback& create_cb, bool /*wait*/,
|
||||
bool advise_erase, bool& is_in_sec_cache) {
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle;
|
||||
kept_in_sec_cache = false;
|
||||
is_in_sec_cache = false;
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
if (lru_handle == nullptr) {
|
||||
return nullptr;
|
||||
@@ -63,15 +64,12 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
ptr = reinterpret_cast<CacheAllocationPtr*>(handle_value);
|
||||
handle_value_charge = cache_->GetCharge(lru_handle);
|
||||
}
|
||||
MemoryAllocator* allocator = cache_options_.memory_allocator.get();
|
||||
|
||||
Status s;
|
||||
Cache::ObjectPtr value{nullptr};
|
||||
void* value{nullptr};
|
||||
size_t charge{0};
|
||||
if (cache_options_.compression_type == kNoCompression ||
|
||||
cache_options_.do_not_compress_roles.Contains(helper->role)) {
|
||||
s = helper->create_cb(Slice(ptr->get(), handle_value_charge),
|
||||
create_context, allocator, &value, &charge);
|
||||
if (cache_options_.compression_type == kNoCompression) {
|
||||
s = create_cb(ptr->get(), handle_value_charge, &value, &charge);
|
||||
} else {
|
||||
UncompressionContext uncompression_context(cache_options_.compression_type);
|
||||
UncompressionInfo uncompression_info(uncompression_context,
|
||||
@@ -81,14 +79,14 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
size_t uncompressed_size{0};
|
||||
CacheAllocationPtr uncompressed = UncompressData(
|
||||
uncompression_info, (char*)ptr->get(), handle_value_charge,
|
||||
&uncompressed_size, cache_options_.compress_format_version, allocator);
|
||||
&uncompressed_size, cache_options_.compress_format_version,
|
||||
cache_options_.memory_allocator.get());
|
||||
|
||||
if (!uncompressed) {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
|
||||
return nullptr;
|
||||
}
|
||||
s = helper->create_cb(Slice(uncompressed.get(), uncompressed_size),
|
||||
create_context, allocator, &value, &charge);
|
||||
s = create_cb(uncompressed.get(), uncompressed_size, &value, &charge);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -100,41 +98,32 @@ std::unique_ptr<SecondaryCacheResultHandle> CompressedSecondaryCache::Lookup(
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
|
||||
// Insert a dummy handle.
|
||||
cache_
|
||||
->Insert(key, /*obj=*/nullptr,
|
||||
GetHelper(cache_options_.enable_custom_split_merge),
|
||||
/*charge=*/0)
|
||||
->Insert(key, /*value=*/nullptr, /*charge=*/0,
|
||||
GetDeletionCallback(cache_options_.enable_custom_split_merge))
|
||||
.PermitUncheckedError();
|
||||
} else {
|
||||
kept_in_sec_cache = true;
|
||||
is_in_sec_cache = true;
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
handle.reset(new CompressedSecondaryCacheResultHandle(value, charge));
|
||||
return handle;
|
||||
}
|
||||
|
||||
Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
bool force_insert) {
|
||||
Status CompressedSecondaryCache::Insert(const Slice& key, void* value,
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
if (value == nullptr) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
|
||||
if (disable_cache_) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
auto internal_helper = GetHelper(cache_options_.enable_custom_split_merge);
|
||||
if (!force_insert) {
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
if (lru_handle == nullptr) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_dummy_count, 1);
|
||||
// Insert a dummy handle if the handle is evicted for the first time.
|
||||
return cache_->Insert(key, /*obj=*/nullptr, internal_helper,
|
||||
/*charge=*/0);
|
||||
} else {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
Cache::DeleterFn del_cb =
|
||||
GetDeletionCallback(cache_options_.enable_custom_split_merge);
|
||||
if (lru_handle == nullptr) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_dummy_count, 1);
|
||||
// Insert a dummy handle if the handle is evicted for the first time.
|
||||
return cache_->Insert(key, /*value=*/nullptr, /*charge=*/0, del_cb);
|
||||
} else {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
}
|
||||
|
||||
size_t size = (*helper->size_cb)(value);
|
||||
@@ -148,12 +137,10 @@ Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
Slice val(ptr.get(), size);
|
||||
|
||||
std::string compressed_val;
|
||||
if (cache_options_.compression_type != kNoCompression &&
|
||||
!cache_options_.do_not_compress_roles.Contains(helper->role)) {
|
||||
if (cache_options_.compression_type != kNoCompression) {
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_uncompressed_bytes, size);
|
||||
CompressionOptions compression_opts;
|
||||
CompressionContext compression_context(cache_options_.compression_type,
|
||||
compression_opts);
|
||||
CompressionContext compression_context(cache_options_.compression_type);
|
||||
uint64_t sample_for_compression{0};
|
||||
CompressionInfo compression_info(
|
||||
compression_opts, compression_context, CompressionDict::GetEmptyDict(),
|
||||
@@ -182,10 +169,10 @@ Status CompressedSecondaryCache::Insert(const Slice& key,
|
||||
size_t charge{0};
|
||||
CacheValueChunk* value_chunks_head =
|
||||
SplitValueIntoChunks(val, cache_options_.compression_type, charge);
|
||||
return cache_->Insert(key, value_chunks_head, internal_helper, charge);
|
||||
return cache_->Insert(key, value_chunks_head, charge, del_cb);
|
||||
} else {
|
||||
CacheAllocationPtr* buf = new CacheAllocationPtr(std::move(ptr));
|
||||
return cache_->Insert(key, buf, internal_helper, size);
|
||||
return cache_->Insert(key, buf, size, del_cb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +182,6 @@ Status CompressedSecondaryCache::SetCapacity(size_t capacity) {
|
||||
MutexLock l(&capacity_mutex_);
|
||||
cache_options_.capacity = capacity;
|
||||
cache_->SetCapacity(capacity);
|
||||
disable_cache_ = capacity == 0;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -290,43 +276,50 @@ CacheAllocationPtr CompressedSecondaryCache::MergeChunksIntoValue(
|
||||
return ptr;
|
||||
}
|
||||
|
||||
const Cache::CacheItemHelper* CompressedSecondaryCache::GetHelper(
|
||||
bool enable_custom_split_merge) const {
|
||||
Cache::DeleterFn CompressedSecondaryCache::GetDeletionCallback(
|
||||
bool enable_custom_split_merge) {
|
||||
if (enable_custom_split_merge) {
|
||||
static const Cache::CacheItemHelper kHelper{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr obj, MemoryAllocator* /*alloc*/) {
|
||||
CacheValueChunk* chunks_head = static_cast<CacheValueChunk*>(obj);
|
||||
while (chunks_head != nullptr) {
|
||||
CacheValueChunk* tmp_chunk = chunks_head;
|
||||
chunks_head = chunks_head->next;
|
||||
tmp_chunk->Free();
|
||||
obj = nullptr;
|
||||
};
|
||||
}};
|
||||
return &kHelper;
|
||||
return [](const Slice& /*key*/, void* obj) {
|
||||
CacheValueChunk* chunks_head = reinterpret_cast<CacheValueChunk*>(obj);
|
||||
while (chunks_head != nullptr) {
|
||||
CacheValueChunk* tmp_chunk = chunks_head;
|
||||
chunks_head = chunks_head->next;
|
||||
tmp_chunk->Free();
|
||||
obj = nullptr;
|
||||
};
|
||||
};
|
||||
} else {
|
||||
static const Cache::CacheItemHelper kHelper{
|
||||
CacheEntryRole::kMisc,
|
||||
[](Cache::ObjectPtr obj, MemoryAllocator* /*alloc*/) {
|
||||
delete static_cast<CacheAllocationPtr*>(obj);
|
||||
obj = nullptr;
|
||||
}};
|
||||
return &kHelper;
|
||||
return [](const Slice& /*key*/, void* obj) {
|
||||
delete reinterpret_cast<CacheAllocationPtr*>(obj);
|
||||
obj = nullptr;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<SecondaryCache>
|
||||
CompressedSecondaryCacheOptions::MakeSharedSecondaryCache() const {
|
||||
return std::make_shared<CompressedSecondaryCache>(*this);
|
||||
std::shared_ptr<SecondaryCache> NewCompressedSecondaryCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
CompressionType compression_type, uint32_t compress_format_version,
|
||||
bool enable_custom_split_merge) {
|
||||
return std::make_shared<CompressedSecondaryCache>(
|
||||
capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
|
||||
low_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, compression_type, compress_format_version,
|
||||
enable_custom_split_merge);
|
||||
}
|
||||
|
||||
Status CompressedSecondaryCache::Deflate(size_t decrease) {
|
||||
return cache_res_mgr_->UpdateCacheReservation(decrease, /*increase=*/true);
|
||||
}
|
||||
|
||||
Status CompressedSecondaryCache::Inflate(size_t increase) {
|
||||
return cache_res_mgr_->UpdateCacheReservation(increase, /*increase=*/false);
|
||||
std::shared_ptr<SecondaryCache> NewCompressedSecondaryCache(
|
||||
const CompressedSecondaryCacheOptions& opts) {
|
||||
// The secondary_cache is disabled for this LRUCache instance.
|
||||
assert(opts.secondary_cache == nullptr);
|
||||
return NewCompressedSecondaryCache(
|
||||
opts.capacity, opts.num_shard_bits, opts.strict_capacity_limit,
|
||||
opts.high_pri_pool_ratio, opts.low_pri_pool_ratio, opts.memory_allocator,
|
||||
opts.use_adaptive_mutex, opts.metadata_charge_policy,
|
||||
opts.compression_type, opts.compress_format_version,
|
||||
opts.enable_custom_split_merge);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+21
-24
@@ -9,9 +9,8 @@
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "cache/lru_cache.h"
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -22,7 +21,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
|
||||
public:
|
||||
CompressedSecondaryCacheResultHandle(Cache::ObjectPtr value, size_t size)
|
||||
CompressedSecondaryCacheResultHandle(void* value, size_t size)
|
||||
: value_(value), size_(size) {}
|
||||
~CompressedSecondaryCacheResultHandle() override = default;
|
||||
|
||||
@@ -35,12 +34,12 @@ class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
|
||||
|
||||
void Wait() override {}
|
||||
|
||||
Cache::ObjectPtr Value() override { return value_; }
|
||||
void* Value() override { return value_; }
|
||||
|
||||
size_t Size() override { return size_; }
|
||||
|
||||
private:
|
||||
Cache::ObjectPtr value_;
|
||||
void* value_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
@@ -70,20 +69,26 @@ class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
|
||||
|
||||
class CompressedSecondaryCache : public SecondaryCache {
|
||||
public:
|
||||
explicit CompressedSecondaryCache(
|
||||
const CompressedSecondaryCacheOptions& opts);
|
||||
CompressedSecondaryCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDefaultCacheMetadataChargePolicy,
|
||||
CompressionType compression_type = CompressionType::kLZ4Compression,
|
||||
uint32_t compress_format_version = 2,
|
||||
bool enable_custom_split_merge = false);
|
||||
~CompressedSecondaryCache() override;
|
||||
|
||||
const char* Name() const override { return "CompressedSecondaryCache"; }
|
||||
|
||||
Status Insert(const Slice& key, Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
bool force_insert) override;
|
||||
Status Insert(const Slice& key, void* value,
|
||||
const Cache::CacheItemHelper* helper) override;
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
const Slice& key, const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context, bool /*wait*/, bool advise_erase,
|
||||
bool& kept_in_sec_cache) override;
|
||||
const Slice& key, const Cache::CreateCallback& create_cb, bool /*wait*/,
|
||||
bool advise_erase, bool& is_in_sec_cache) override;
|
||||
|
||||
bool SupportForceErase() const override { return true; }
|
||||
|
||||
@@ -95,16 +100,10 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
|
||||
Status GetCapacity(size_t& capacity) override;
|
||||
|
||||
Status Deflate(size_t decrease) override;
|
||||
|
||||
Status Inflate(size_t increase) override;
|
||||
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
size_t TEST_GetUsage() { return cache_->GetUsage(); }
|
||||
|
||||
private:
|
||||
friend class CompressedSecondaryCacheTestBase;
|
||||
friend class CompressedSecondaryCacheTest;
|
||||
static constexpr std::array<uint16_t, 8> malloc_bin_sizes_{
|
||||
128, 256, 512, 1024, 2048, 4096, 8192, 16384};
|
||||
|
||||
@@ -130,13 +129,11 @@ class CompressedSecondaryCache : public SecondaryCache {
|
||||
CacheAllocationPtr MergeChunksIntoValue(const void* chunks_head,
|
||||
size_t& charge);
|
||||
|
||||
// TODO: clean up to use cleaner interfaces in typed_cache.h
|
||||
const Cache::CacheItemHelper* GetHelper(bool enable_custom_split_merge) const;
|
||||
// An implementation of Cache::DeleterFn.
|
||||
static Cache::DeleterFn GetDeletionCallback(bool enable_custom_split_merge);
|
||||
std::shared_ptr<Cache> cache_;
|
||||
CompressedSecondaryCacheOptions cache_options_;
|
||||
mutable port::Mutex capacity_mutex_;
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
|
||||
bool disable_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+246
-423
File diff suppressed because it is too large.
Load diff
Vendored
+580
@@ -0,0 +1,580 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "cache/fast_lru_cache.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "port/lang.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace fast_lru_cache {
|
||||
|
||||
LRUHandleTable::LRUHandleTable(int hash_bits)
|
||||
: length_bits_(hash_bits),
|
||||
length_bits_mask_((uint32_t{1} << length_bits_) - 1),
|
||||
occupancy_(0),
|
||||
occupancy_limit_(static_cast<uint32_t>((uint32_t{1} << length_bits_) *
|
||||
kStrictLoadFactor)),
|
||||
array_(new LRUHandle[size_t{1} << length_bits_]) {
|
||||
assert(hash_bits <= 32);
|
||||
}
|
||||
|
||||
LRUHandleTable::~LRUHandleTable() {
|
||||
ApplyToEntriesRange([](LRUHandle* h) { h->FreeData(); }, 0, GetTableSize());
|
||||
}
|
||||
|
||||
LRUHandle* LRUHandleTable::Lookup(const Slice& key, uint32_t hash) {
|
||||
int probe = 0;
|
||||
int slot = FindVisibleElement(key, hash, probe, 0);
|
||||
return (slot == -1) ? nullptr : &array_[slot];
|
||||
}
|
||||
|
||||
LRUHandle* LRUHandleTable::Insert(LRUHandle* h, LRUHandle** old) {
|
||||
int probe = 0;
|
||||
int slot = FindVisibleElementOrAvailableSlot(h->key(), h->hash, probe,
|
||||
1 /*displacement*/);
|
||||
*old = nullptr;
|
||||
if (slot == -1) {
|
||||
// TODO(Guido) Don't we need to roll back displacements here?
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (array_[slot].IsEmpty() || array_[slot].IsTombstone()) {
|
||||
bool empty = array_[slot].IsEmpty();
|
||||
Assign(slot, h);
|
||||
LRUHandle* new_entry = &array_[slot];
|
||||
if (empty) {
|
||||
// This used to be an empty slot.
|
||||
return new_entry;
|
||||
}
|
||||
// It used to be a tombstone, so there may already be a copy of the
|
||||
// key in the table.
|
||||
slot = FindVisibleElement(h->key(), h->hash, probe, 0 /*displacement*/);
|
||||
if (slot == -1) {
|
||||
// No existing copy of the key.
|
||||
return new_entry;
|
||||
}
|
||||
*old = &array_[slot];
|
||||
return new_entry;
|
||||
} else {
|
||||
// There is an existing copy of the key.
|
||||
*old = &array_[slot];
|
||||
// Find an available slot for the new element.
|
||||
array_[slot].displacements++;
|
||||
slot = FindAvailableSlot(h->key(), probe, 1 /*displacement*/);
|
||||
if (slot == -1) {
|
||||
// No available slots. Roll back displacements.
|
||||
probe = 0;
|
||||
slot = FindVisibleElement(h->key(), h->hash, probe, -1);
|
||||
array_[slot].displacements--;
|
||||
FindAvailableSlot(h->key(), probe, -1);
|
||||
return nullptr;
|
||||
}
|
||||
Assign(slot, h);
|
||||
return &array_[slot];
|
||||
}
|
||||
}
|
||||
|
||||
void LRUHandleTable::Remove(LRUHandle* h) {
|
||||
assert(h->next == nullptr &&
|
||||
h->prev == nullptr); // Already off the LRU list.
|
||||
int probe = 0;
|
||||
FindSlot(
|
||||
h->key(), [&h](LRUHandle* e) { return e == h; }, probe,
|
||||
-1 /*displacement*/);
|
||||
h->SetIsVisible(false);
|
||||
h->SetIsElement(false);
|
||||
occupancy_--;
|
||||
}
|
||||
|
||||
void LRUHandleTable::Assign(int slot, LRUHandle* h) {
|
||||
LRUHandle* dst = &array_[slot];
|
||||
uint32_t disp = dst->displacements;
|
||||
*dst = *h;
|
||||
dst->displacements = disp;
|
||||
dst->SetIsVisible(true);
|
||||
dst->SetIsElement(true);
|
||||
occupancy_++;
|
||||
}
|
||||
|
||||
void LRUHandleTable::Exclude(LRUHandle* h) { h->SetIsVisible(false); }
|
||||
|
||||
int LRUHandleTable::FindVisibleElement(const Slice& key, uint32_t hash,
|
||||
int& probe, int displacement) {
|
||||
return FindSlot(
|
||||
key,
|
||||
[&](LRUHandle* h) { return h->Matches(key, hash) && h->IsVisible(); },
|
||||
probe, displacement);
|
||||
}
|
||||
|
||||
int LRUHandleTable::FindAvailableSlot(const Slice& key, int& probe,
|
||||
int displacement) {
|
||||
return FindSlot(
|
||||
key, [](LRUHandle* h) { return h->IsEmpty() || h->IsTombstone(); }, probe,
|
||||
displacement);
|
||||
}
|
||||
|
||||
int LRUHandleTable::FindVisibleElementOrAvailableSlot(const Slice& key,
|
||||
uint32_t hash, int& probe,
|
||||
int displacement) {
|
||||
return FindSlot(
|
||||
key,
|
||||
[&](LRUHandle* h) {
|
||||
return h->IsEmpty() || h->IsTombstone() ||
|
||||
(h->Matches(key, hash) && h->IsVisible());
|
||||
},
|
||||
probe, displacement);
|
||||
}
|
||||
|
||||
inline int LRUHandleTable::FindSlot(const Slice& key,
|
||||
std::function<bool(LRUHandle*)> cond,
|
||||
int& probe, int displacement) {
|
||||
uint32_t base = ModTableSize(Hash(key.data(), key.size(), kProbingSeed1));
|
||||
uint32_t increment =
|
||||
ModTableSize((Hash(key.data(), key.size(), kProbingSeed2) << 1) | 1);
|
||||
uint32_t current = ModTableSize(base + probe * increment);
|
||||
while (true) {
|
||||
LRUHandle* h = &array_[current];
|
||||
probe++;
|
||||
if (current == base && probe > 1) {
|
||||
// We looped back.
|
||||
return -1;
|
||||
}
|
||||
if (cond(h)) {
|
||||
return current;
|
||||
}
|
||||
if (h->IsEmpty()) {
|
||||
// We check emptyness after the condition, because
|
||||
// the condition may be emptyness.
|
||||
return -1;
|
||||
}
|
||||
h->displacements += displacement;
|
||||
current = ModTableSize(current + increment);
|
||||
}
|
||||
}
|
||||
|
||||
LRUCacheShard::LRUCacheShard(size_t capacity, size_t estimated_value_size,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
: CacheShardBase(metadata_charge_policy),
|
||||
capacity_(capacity),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
table_(
|
||||
CalcHashBits(capacity, estimated_value_size, metadata_charge_policy)),
|
||||
usage_(0),
|
||||
lru_usage_(0) {
|
||||
// Make empty circular linked list.
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
lru_low_pri_ = &lru_;
|
||||
}
|
||||
|
||||
void LRUCacheShard::EraseUnRefEntries() {
|
||||
autovector<LRUHandle> last_reference_list;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
while (lru_.next != &lru_) {
|
||||
LRUHandle* old = lru_.next;
|
||||
// LRU list contains only elements which can be evicted.
|
||||
assert(old->IsVisible() && !old->HasRefs());
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old);
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
last_reference_list.push_back(*old);
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entries here outside of mutex for performance reasons.
|
||||
for (auto& h : last_reference_list) {
|
||||
h.FreeData();
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
size_t average_entries_per_lock, size_t* state) {
|
||||
// The state is essentially going to be the starting hash, which works
|
||||
// nicely even if we resize between calls because we use upper-most
|
||||
// hash bits for table indexes.
|
||||
DMutexLock l(mutex_);
|
||||
size_t length_bits = table_.GetLengthBits();
|
||||
size_t length = table_.GetTableSize();
|
||||
|
||||
assert(average_entries_per_lock > 0);
|
||||
// Assuming we are called with same average_entries_per_lock repeatedly,
|
||||
// this simplifies some logic (index_end will not overflow).
|
||||
assert(average_entries_per_lock < length || *state == 0);
|
||||
|
||||
size_t index_begin = *state >> (sizeof(size_t) * 8u - length_bits);
|
||||
size_t index_end = index_begin + average_entries_per_lock;
|
||||
if (index_end >= length) {
|
||||
// Going to end
|
||||
index_end = length;
|
||||
*state = SIZE_MAX;
|
||||
} else {
|
||||
*state = index_end << (sizeof(size_t) * 8u - length_bits);
|
||||
}
|
||||
|
||||
table_.ApplyToEntriesRange(
|
||||
[callback,
|
||||
metadata_charge_policy = metadata_charge_policy_](LRUHandle* h) {
|
||||
callback(h->key(), h->value, h->GetCharge(metadata_charge_policy),
|
||||
h->deleter);
|
||||
},
|
||||
index_begin, index_end);
|
||||
}
|
||||
|
||||
void LRUCacheShard::LRU_Remove(LRUHandle* h) {
|
||||
assert(h->next != nullptr);
|
||||
assert(h->prev != nullptr);
|
||||
h->next->prev = h->prev;
|
||||
h->prev->next = h->next;
|
||||
h->prev = h->next = nullptr;
|
||||
assert(lru_usage_ >= h->total_charge);
|
||||
lru_usage_ -= h->total_charge;
|
||||
}
|
||||
|
||||
void LRUCacheShard::LRU_Insert(LRUHandle* h) {
|
||||
assert(h->next == nullptr);
|
||||
assert(h->prev == nullptr);
|
||||
// Insert h to head of LRU list.
|
||||
h->next = &lru_;
|
||||
h->prev = lru_.prev;
|
||||
h->prev->next = h;
|
||||
h->next->prev = h;
|
||||
lru_usage_ += h->total_charge;
|
||||
}
|
||||
|
||||
void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
autovector<LRUHandle>* deleted) {
|
||||
while ((usage_ + charge) > capacity_ && lru_.next != &lru_) {
|
||||
LRUHandle* old = lru_.next;
|
||||
// LRU list contains only elements which can be evicted.
|
||||
assert(old->IsVisible() && !old->HasRefs());
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old);
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
deleted->push_back(*old);
|
||||
}
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::CalcEstimatedHandleCharge(
|
||||
size_t estimated_value_size,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
LRUHandle h;
|
||||
h.CalcTotalCharge(estimated_value_size, metadata_charge_policy);
|
||||
return h.total_charge;
|
||||
}
|
||||
|
||||
int LRUCacheShard::CalcHashBits(
|
||||
size_t capacity, size_t estimated_value_size,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
size_t handle_charge =
|
||||
CalcEstimatedHandleCharge(estimated_value_size, metadata_charge_policy);
|
||||
assert(handle_charge > 0);
|
||||
uint32_t num_entries =
|
||||
static_cast<uint32_t>(capacity / (kLoadFactor * handle_charge)) + 1;
|
||||
assert(num_entries <= uint32_t{1} << 31);
|
||||
return FloorLog2((num_entries << 1) - 1);
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
autovector<LRUHandle> last_reference_list;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
if (capacity > capacity_) {
|
||||
assert(false); // Not supported.
|
||||
}
|
||||
capacity_ = capacity;
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
}
|
||||
|
||||
// Free the entries here outside of mutex for performance reasons.
|
||||
for (auto& h : last_reference_list) {
|
||||
h.FreeData();
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
DMutexLock l(mutex_);
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge, Cache::DeleterFn deleter,
|
||||
LRUHandle** handle, Cache::Priority /*priority*/) {
|
||||
if (key.size() != kCacheKeySize) {
|
||||
return Status::NotSupported("FastLRUCache only supports key size " +
|
||||
std::to_string(kCacheKeySize) + "B");
|
||||
}
|
||||
|
||||
LRUHandle tmp;
|
||||
tmp.value = value;
|
||||
tmp.deleter = deleter;
|
||||
tmp.hash = hash;
|
||||
tmp.CalcTotalCharge(charge, metadata_charge_policy_);
|
||||
for (int i = 0; i < kCacheKeySize; i++) {
|
||||
tmp.key_data[i] = key.data()[i];
|
||||
}
|
||||
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle> last_reference_list;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
assert(table_.GetOccupancy() <= table_.GetOccupancyLimit());
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty.
|
||||
EvictFromLRU(tmp.total_charge, &last_reference_list);
|
||||
if ((usage_ + tmp.total_charge > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) ||
|
||||
table_.GetOccupancy() == table_.GetOccupancyLimit()) {
|
||||
// There are two measures of capacity:
|
||||
// - Space (or charge) capacity: The maximum possible sum of the charges
|
||||
// of the elements.
|
||||
// - Table capacity: The number of slots in the hash table.
|
||||
// These are incomparable, in the sense that one doesn't imply the other.
|
||||
// Typically we will reach space capacity before table capacity---
|
||||
// if the user always inserts values with size equal to
|
||||
// estimated_value_size, then at most a kLoadFactor fraction of slots
|
||||
// will ever be occupied. But in some cases we may reach table capacity
|
||||
// before space capacity---if the user initially claims a very large
|
||||
// estimated_value_size but then inserts tiny values, more elements than
|
||||
// initially estimated will be inserted.
|
||||
|
||||
// TODO(Guido) Some tests (at least two from cache_test, as well as the
|
||||
// stress tests) currently assume the table capacity is unbounded.
|
||||
if (handle == nullptr) {
|
||||
// Don't insert the entry but still return ok, as if the entry inserted
|
||||
// into cache and get evicted immediately.
|
||||
last_reference_list.push_back(tmp);
|
||||
} else {
|
||||
if (table_.GetOccupancy() == table_.GetOccupancyLimit()) {
|
||||
// TODO: Consider using a distinct status for this case, but usually
|
||||
// it will be handled the same way as reaching charge capacity limit
|
||||
s = Status::MemoryLimit(
|
||||
"Insert failed because all slots in the hash table are full.");
|
||||
} else {
|
||||
s = Status::MemoryLimit(
|
||||
"Insert failed because the total charge has exceeded the "
|
||||
"capacity.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old;
|
||||
LRUHandle* h = table_.Insert(&tmp, &old);
|
||||
assert(h != nullptr); // We're below occupancy, so this insertion should
|
||||
// never fail.
|
||||
usage_ += h->total_charge;
|
||||
if (old != nullptr) {
|
||||
s = Status::OkOverwritten();
|
||||
assert(old->IsVisible());
|
||||
table_.Exclude(old);
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0.
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old);
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
last_reference_list.push_back(*old);
|
||||
}
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(h);
|
||||
} else {
|
||||
// If caller already holds a ref, no need to take one here.
|
||||
if (!h->HasRefs()) {
|
||||
h->Ref();
|
||||
}
|
||||
*handle = h;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entries here outside of mutex for performance reasons.
|
||||
for (auto& h : last_reference_list) {
|
||||
h.FreeData();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
LRUHandle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
||||
LRUHandle* h = nullptr;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
h = table_.Lookup(key, hash);
|
||||
if (h != nullptr) {
|
||||
assert(h->IsVisible());
|
||||
if (!h->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external
|
||||
// references.
|
||||
LRU_Remove(h);
|
||||
}
|
||||
h->Ref();
|
||||
}
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
bool LRUCacheShard::Ref(LRUHandle* h) {
|
||||
DMutexLock l(mutex_);
|
||||
// To create another reference - entry must be already externally referenced.
|
||||
assert(h->HasRefs());
|
||||
h->Ref();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LRUCacheShard::Release(LRUHandle* h, bool erase_if_last_ref) {
|
||||
if (h == nullptr) {
|
||||
return false;
|
||||
}
|
||||
LRUHandle copy;
|
||||
bool last_reference = false;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
last_reference = h->Unref();
|
||||
if (last_reference && h->IsVisible()) {
|
||||
// The item is still in cache, and nobody else holds a reference to it.
|
||||
if (usage_ > capacity_ || erase_if_last_ref) {
|
||||
// The LRU list must be empty since the cache is full.
|
||||
assert(lru_.next == &lru_ || erase_if_last_ref);
|
||||
// Take this opportunity and remove the item.
|
||||
table_.Remove(h);
|
||||
} else {
|
||||
// Put the item back on the LRU list, and don't free it.
|
||||
LRU_Insert(h);
|
||||
last_reference = false;
|
||||
}
|
||||
}
|
||||
// If it was the last reference, then decrement the cache usage.
|
||||
if (last_reference) {
|
||||
assert(usage_ >= h->total_charge);
|
||||
usage_ -= h->total_charge;
|
||||
copy = *h;
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entry here outside of mutex for performance reasons.
|
||||
if (last_reference) {
|
||||
copy.FreeData();
|
||||
}
|
||||
return last_reference;
|
||||
}
|
||||
|
||||
void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
LRUHandle copy;
|
||||
bool last_reference = false;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
LRUHandle* h = table_.Lookup(key, hash);
|
||||
if (h != nullptr) {
|
||||
table_.Exclude(h);
|
||||
if (!h->HasRefs()) {
|
||||
// The entry is in LRU since it's in cache and has no external
|
||||
// references.
|
||||
LRU_Remove(h);
|
||||
table_.Remove(h);
|
||||
assert(usage_ >= h->total_charge);
|
||||
usage_ -= h->total_charge;
|
||||
last_reference = true;
|
||||
copy = *h;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Free the entry here outside of mutex for performance reasons.
|
||||
// last_reference will only be true if e != nullptr.
|
||||
if (last_reference) {
|
||||
copy.FreeData();
|
||||
}
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetUsage() const {
|
||||
DMutexLock l(mutex_);
|
||||
return usage_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetPinnedUsage() const {
|
||||
DMutexLock l(mutex_);
|
||||
assert(usage_ >= lru_usage_);
|
||||
return usage_ - lru_usage_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetOccupancyCount() const {
|
||||
DMutexLock l(mutex_);
|
||||
return table_.GetOccupancy();
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetTableAddressCount() const {
|
||||
DMutexLock l(mutex_);
|
||||
return table_.GetTableSize();
|
||||
}
|
||||
|
||||
LRUCache::LRUCache(size_t capacity, size_t estimated_value_size,
|
||||
int num_shard_bits, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
nullptr /*allocator*/) {
|
||||
assert(estimated_value_size > 0 ||
|
||||
metadata_charge_policy != kDontChargeCacheMetadata);
|
||||
size_t per_shard = GetPerShardCapacity();
|
||||
InitShards([=](LRUCacheShard* cs) {
|
||||
new (cs) LRUCacheShard(per_shard, estimated_value_size,
|
||||
strict_capacity_limit, metadata_charge_policy);
|
||||
});
|
||||
}
|
||||
|
||||
void* LRUCache::Value(Handle* handle) {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->value;
|
||||
}
|
||||
|
||||
size_t LRUCache::GetCharge(Handle* handle) const {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->GetCharge(
|
||||
GetShard(0).metadata_charge_policy_);
|
||||
}
|
||||
|
||||
Cache::DeleterFn LRUCache::GetDeleter(Handle* handle) const {
|
||||
auto h = reinterpret_cast<const LRUHandle*>(handle);
|
||||
return h->deleter;
|
||||
}
|
||||
|
||||
} // namespace fast_lru_cache
|
||||
|
||||
std::shared_ptr<Cache> NewFastLRUCache(
|
||||
size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // The cache cannot be sharded into too many fine pieces.
|
||||
}
|
||||
if (num_shard_bits < 0) {
|
||||
num_shard_bits = GetDefaultCacheShardBits(capacity);
|
||||
}
|
||||
return std::make_shared<fast_lru_cache::LRUCache>(
|
||||
capacity, estimated_value_size, num_shard_bits, strict_capacity_limit,
|
||||
metadata_charge_policy);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+477
@@ -0,0 +1,477 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/sharded_cache.h"
|
||||
#include "port/lang.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace fast_lru_cache {
|
||||
|
||||
// Forward declaration of friend class.
|
||||
class FastLRUCacheTest;
|
||||
|
||||
// LRU cache implementation using an open-address hash table.
|
||||
//
|
||||
// Every slot in the hash table is an LRUHandle. Because handles can be
|
||||
// referenced externally, we can't discard them immediately once they are
|
||||
// deleted (via a delete or an LRU eviction) or replaced by a new version
|
||||
// (via an insert of the same key). The state of an element is defined by
|
||||
// the following two properties:
|
||||
// (R) Referenced: An element can be referenced externally (refs > 0), or not.
|
||||
// Importantly, an element can be evicted if and only if it's not
|
||||
// referenced. In particular, when an element becomes referenced, it's
|
||||
// temporarily taken out of the LRU list until all references to it
|
||||
// are dropped.
|
||||
// (V) Visible: An element can visible for lookups (IS_VISIBLE set), or not.
|
||||
// Initially, every element is visible. An element that is not visible is
|
||||
// called a ghost.
|
||||
// These properties induce 4 different states, with transitions defined as
|
||||
// follows:
|
||||
// - V --> not V: When a visible element is deleted or replaced by a new
|
||||
// version.
|
||||
// - Not V --> V: This cannot happen. A ghost remains in that state until it's
|
||||
// not referenced any more, at which point it's ready to be removed from the
|
||||
// hash table. (A ghost simply waits to transition to the afterlife---it will
|
||||
// never be visible again.)
|
||||
// - R --> not R: When all references to an element are dropped.
|
||||
// - Not R --> R: When an unreferenced element becomes referenced. This can only
|
||||
// happen if the element is V, since references to an element can only be
|
||||
// created when it's visible.
|
||||
//
|
||||
// Internally, the cache uses an open-addressed hash table to index the handles.
|
||||
// We use tombstone counters to keep track of displacements.
|
||||
// Because of the tombstones and the two possible visibility states of an
|
||||
// element, the table slots can be in 4 different states:
|
||||
// 1. Visible element (IS_ELEMENT set and IS_VISIBLE set): The slot contains a
|
||||
// key-value element.
|
||||
// 2. Ghost element (IS_ELEMENT set and IS_VISIBLE unset): The slot contains an
|
||||
// element that has been removed, but it's still referenced. It's invisible
|
||||
// to lookups.
|
||||
// 3. Tombstone (IS_ELEMENT unset and displacements > 0): The slot contains a
|
||||
// tombstone.
|
||||
// 4. Empty (IS_ELEMENT unset and displacements == 0): The slot is unused.
|
||||
// A slot that is an element can further have IS_VISIBLE set or not.
|
||||
// When a ghost is removed from the table, it can either transition to being a
|
||||
// tombstone or an empty slot, depending on the number of displacements of the
|
||||
// slot. In any case, the slot becomes available. When a handle is inserted
|
||||
// into that slot, it becomes a visible element again.
|
||||
|
||||
// The load factor p is a real number in (0, 1) such that at all
|
||||
// times at most a fraction p of all slots, without counting tombstones,
|
||||
// are occupied by elements. This means that the probability that a
|
||||
// random probe hits an empty slot is at most p, and thus at most 1/p probes
|
||||
// are required on average. For example, p = 70% implies that between 1 and 2
|
||||
// probes are needed on average (bear in mind that this reasoning doesn't
|
||||
// consider the effects of clustering over time).
|
||||
// Because the size of the hash table is always rounded up to the next
|
||||
// power of 2, p is really an upper bound on the actual load factor---the
|
||||
// actual load factor is anywhere between p/2 and p. This is a bit wasteful,
|
||||
// but bear in mind that slots only hold metadata, not actual values.
|
||||
// Since space cost is dominated by the values (the LSM blocks),
|
||||
// overprovisioning the table with metadata only increases the total cache space
|
||||
// usage by a tiny fraction.
|
||||
constexpr double kLoadFactor = 0.35;
|
||||
|
||||
// The user can exceed kLoadFactor if the sizes of the inserted values don't
|
||||
// match estimated_value_size, or if strict_capacity_limit == false. To
|
||||
// avoid performance to plunge, we set a strict upper bound on the load factor.
|
||||
constexpr double kStrictLoadFactor = 0.7;
|
||||
|
||||
// Arbitrary seeds.
|
||||
constexpr uint32_t kProbingSeed1 = 0xbc9f1d34;
|
||||
constexpr uint32_t kProbingSeed2 = 0x7a2bb9d5;
|
||||
|
||||
// An experimental (under development!) alternative to LRUCache
|
||||
|
||||
struct LRUHandle {
|
||||
void* value;
|
||||
Cache::DeleterFn deleter;
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t total_charge; // TODO(opt): Only allow uint32_t?
|
||||
// The hash of key(). Used for fast sharding and comparisons.
|
||||
uint32_t hash;
|
||||
// The number of external refs to this entry.
|
||||
uint32_t refs;
|
||||
|
||||
enum Flags : uint8_t {
|
||||
// Whether the handle is visible to Lookups.
|
||||
IS_VISIBLE = (1 << 0),
|
||||
// Whether the slot is in use by an element.
|
||||
IS_ELEMENT = (1 << 1),
|
||||
};
|
||||
uint8_t flags;
|
||||
|
||||
// The number of elements that hash to this slot or a lower one,
|
||||
// but wind up in a higher slot.
|
||||
uint32_t displacements;
|
||||
|
||||
std::array<char, kCacheKeySize> key_data;
|
||||
|
||||
LRUHandle() {
|
||||
value = nullptr;
|
||||
deleter = nullptr;
|
||||
next = nullptr;
|
||||
prev = nullptr;
|
||||
total_charge = 0;
|
||||
hash = 0;
|
||||
refs = 0;
|
||||
flags = 0;
|
||||
displacements = 0;
|
||||
key_data.fill(0);
|
||||
}
|
||||
|
||||
Slice key() const { return Slice(key_data.data(), kCacheKeySize); }
|
||||
|
||||
// For HandleImpl concept
|
||||
uint32_t GetHash() const { return hash; }
|
||||
|
||||
// Increase the reference count by 1.
|
||||
void Ref() { refs++; }
|
||||
|
||||
// Just reduce the reference count by 1. Return true if it was last reference.
|
||||
bool Unref() {
|
||||
assert(refs > 0);
|
||||
refs--;
|
||||
return refs == 0;
|
||||
}
|
||||
|
||||
// Return true if there are external refs, false otherwise.
|
||||
bool HasRefs() const { return refs > 0; }
|
||||
|
||||
bool IsVisible() const { return flags & IS_VISIBLE; }
|
||||
|
||||
void SetIsVisible(bool is_visible) {
|
||||
if (is_visible) {
|
||||
flags |= IS_VISIBLE;
|
||||
} else {
|
||||
flags &= ~IS_VISIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsElement() const { return flags & IS_ELEMENT; }
|
||||
|
||||
void SetIsElement(bool is_element) {
|
||||
if (is_element) {
|
||||
flags |= IS_ELEMENT;
|
||||
} else {
|
||||
flags &= ~IS_ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeData() {
|
||||
assert(refs == 0);
|
||||
if (deleter) {
|
||||
(*deleter)(key(), value);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the memory usage by metadata.
|
||||
inline size_t CalcMetaCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) const {
|
||||
if (metadata_charge_policy != kFullChargeCacheMetadata) {
|
||||
return 0;
|
||||
} else {
|
||||
// #ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
// return malloc_usable_size(
|
||||
// const_cast<void*>(static_cast<const void*>(this)));
|
||||
// #else
|
||||
// TODO(Guido) malloc_usable_size only works when we call it on
|
||||
// a pointer allocated with malloc. Because our handles are all
|
||||
// allocated in a single shot as an array, the user can't call
|
||||
// CalcMetaCharge (or CalcTotalCharge or GetCharge) on a handle
|
||||
// pointer returned by the cache. Moreover, malloc_usable_size
|
||||
// expects a heap-allocated handle, but sometimes in our code we
|
||||
// wish to pass a stack-allocated handle (this is only a performance
|
||||
// concern).
|
||||
// What is the right way to compute metadata charges with pre-allocated
|
||||
// handles?
|
||||
return sizeof(LRUHandle);
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
inline void CalcTotalCharge(
|
||||
size_t charge, CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
total_charge = charge + CalcMetaCharge(metadata_charge_policy);
|
||||
}
|
||||
|
||||
inline size_t GetCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) const {
|
||||
size_t meta_charge = CalcMetaCharge(metadata_charge_policy);
|
||||
assert(total_charge >= meta_charge);
|
||||
return total_charge - meta_charge;
|
||||
}
|
||||
|
||||
inline bool IsEmpty() {
|
||||
return !this->IsElement() && this->displacements == 0;
|
||||
}
|
||||
|
||||
inline bool IsTombstone() {
|
||||
return !this->IsElement() && this->displacements > 0;
|
||||
}
|
||||
|
||||
inline bool Matches(const Slice& some_key, uint32_t some_hash) {
|
||||
return this->IsElement() && this->hash == some_hash &&
|
||||
this->key() == some_key;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class LRUHandleTable {
|
||||
public:
|
||||
explicit LRUHandleTable(int hash_bits);
|
||||
~LRUHandleTable();
|
||||
|
||||
// Returns a pointer to a visible element matching the key/hash, or
|
||||
// nullptr if not present.
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash);
|
||||
|
||||
// Inserts a copy of h into the hash table.
|
||||
// Returns a pointer to the inserted handle, or nullptr if no slot
|
||||
// available was found. If an existing visible element matching the
|
||||
// key/hash is already present in the hash table, the argument old
|
||||
// is set to pointe to it; otherwise, it's set to nullptr.
|
||||
LRUHandle* Insert(LRUHandle* h, LRUHandle** old);
|
||||
|
||||
// Removes h from the hash table. The handle must already be off
|
||||
// the LRU list.
|
||||
void Remove(LRUHandle* h);
|
||||
|
||||
// Turns a visible element h into a ghost (i.e., not visible).
|
||||
void Exclude(LRUHandle* h);
|
||||
|
||||
// Assigns a copy of h to the given slot.
|
||||
void Assign(int slot, LRUHandle* h);
|
||||
|
||||
template <typename T>
|
||||
void ApplyToEntriesRange(T func, size_t index_begin, size_t index_end) {
|
||||
for (size_t i = index_begin; i < index_end; i++) {
|
||||
LRUHandle* h = &array_[i];
|
||||
if (h->IsVisible()) {
|
||||
func(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t GetTableSize() const { return uint32_t{1} << length_bits_; }
|
||||
|
||||
int GetLengthBits() const { return length_bits_; }
|
||||
|
||||
uint32_t GetOccupancyLimit() const { return occupancy_limit_; }
|
||||
|
||||
uint32_t GetOccupancy() const { return occupancy_; }
|
||||
|
||||
// Returns x mod 2^{length_bits_}.
|
||||
uint32_t ModTableSize(uint32_t x) { return x & length_bits_mask_; }
|
||||
|
||||
private:
|
||||
int FindVisibleElement(const Slice& key, uint32_t hash, int& probe,
|
||||
int displacement);
|
||||
|
||||
int FindAvailableSlot(const Slice& key, int& probe, int displacement);
|
||||
|
||||
int FindVisibleElementOrAvailableSlot(const Slice& key, uint32_t hash,
|
||||
int& probe, int displacement);
|
||||
|
||||
// Returns the index of the first slot probed (hashing with
|
||||
// the given key) with a handle e such that cond(e) is true.
|
||||
// Otherwise, if no match is found, returns -1.
|
||||
// For every handle e probed except the final slot, updates
|
||||
// e->displacements += displacement.
|
||||
// The argument probe is modified such that consecutive calls
|
||||
// to FindSlot continue probing right after where the previous
|
||||
// call left.
|
||||
int FindSlot(const Slice& key, std::function<bool(LRUHandle*)> cond,
|
||||
int& probe, int displacement);
|
||||
|
||||
// Number of hash bits used for table index.
|
||||
// The size of the table is 1 << length_bits_.
|
||||
int length_bits_;
|
||||
|
||||
const uint32_t length_bits_mask_;
|
||||
|
||||
// Number of elements in the table.
|
||||
uint32_t occupancy_;
|
||||
|
||||
// Maximum number of elements the user can store in the table.
|
||||
uint32_t occupancy_limit_;
|
||||
|
||||
std::unique_ptr<LRUHandle[]> array_;
|
||||
};
|
||||
|
||||
// A single shard of sharded cache.
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, size_t estimated_value_size,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
|
||||
// For CacheShard concept
|
||||
using HandleImpl = LRUHandle;
|
||||
|
||||
// Keep 32-bit hashing for now (FIXME: upgrade to 64-bit)
|
||||
using HashVal = uint32_t;
|
||||
using HashCref = uint32_t;
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
return Lower32of64(GetSliceNPHash64(key));
|
||||
}
|
||||
static inline uint32_t HashPieceForSharding(HashCref hash) { return hash; }
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
// if current usage is more than new capacity, the function will attempt to
|
||||
// free the needed space.
|
||||
void SetCapacity(size_t capacity);
|
||||
|
||||
// Set the flag to reject insertion if cache if full.
|
||||
void SetStrictCapacityLimit(bool strict_capacity_limit);
|
||||
|
||||
// Like Cache methods, but with an extra "hash" parameter.
|
||||
// Insert an item into the hash table and, if handle is null, insert into
|
||||
// the LRU list. Older items are evicted as necessary. If the cache is full
|
||||
// and free_handle_on_fail is true, the item is deleted and handle is set to
|
||||
// nullptr.
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
Cache::DeleterFn deleter, LRUHandle** handle,
|
||||
Cache::Priority priority);
|
||||
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
LRUHandle** handle, Cache::Priority priority) {
|
||||
return Insert(key, hash, value, charge, helper->del_cb, handle, priority);
|
||||
}
|
||||
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
const Cache::CreateCallback& /*create_cb*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/,
|
||||
Statistics* /*stats*/) {
|
||||
return Lookup(key, hash);
|
||||
}
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash);
|
||||
|
||||
bool Release(LRUHandle* handle, bool /*useful*/, bool erase_if_last_ref) {
|
||||
return Release(handle, erase_if_last_ref);
|
||||
}
|
||||
bool IsReady(LRUHandle* /*handle*/) { return true; }
|
||||
void Wait(LRUHandle* /*handle*/) {}
|
||||
|
||||
bool Ref(LRUHandle* handle);
|
||||
bool Release(LRUHandle* handle, bool erase_if_last_ref = false);
|
||||
void Erase(const Slice& key, uint32_t hash);
|
||||
|
||||
size_t GetUsage() const;
|
||||
size_t GetPinnedUsage() const;
|
||||
size_t GetOccupancyCount() const;
|
||||
size_t GetTableAddressCount() const;
|
||||
|
||||
void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
size_t average_entries_per_lock, size_t* state);
|
||||
|
||||
void EraseUnRefEntries();
|
||||
|
||||
private:
|
||||
friend class LRUCache;
|
||||
friend class FastLRUCacheTest;
|
||||
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Insert(LRUHandle* e);
|
||||
|
||||
// Free some space following strict LRU policy until enough space
|
||||
// to hold (usage_ + charge) is freed or the LRU list is empty
|
||||
// This function is not thread safe - it needs to be executed while
|
||||
// holding the mutex_.
|
||||
void EvictFromLRU(size_t charge, autovector<LRUHandle>* deleted);
|
||||
|
||||
// Returns the charge of a single handle.
|
||||
static size_t CalcEstimatedHandleCharge(
|
||||
size_t estimated_value_size,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
|
||||
// Returns the number of bits used to hash an element in the hash
|
||||
// table.
|
||||
static int CalcHashBits(size_t capacity, size_t estimated_value_size,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
|
||||
// Initialized before use.
|
||||
size_t capacity_;
|
||||
|
||||
// Whether to reject insertion if cache reaches its full capacity.
|
||||
bool strict_capacity_limit_;
|
||||
|
||||
// Dummy head of LRU list.
|
||||
// lru.prev is newest entry, lru.next is oldest entry.
|
||||
// LRU contains items which can be evicted, ie reference only by cache
|
||||
LRUHandle lru_;
|
||||
|
||||
// Pointer to head of low-pri pool in LRU list.
|
||||
LRUHandle* lru_low_pri_;
|
||||
|
||||
// ------------^^^^^^^^^^^^^-----------
|
||||
// Not frequently modified data members
|
||||
// ------------------------------------
|
||||
//
|
||||
// We separate data members that are updated frequently from the ones that
|
||||
// are not frequently updated so that they don't share the same cache line
|
||||
// which will lead into false cache sharing
|
||||
//
|
||||
// ------------------------------------
|
||||
// Frequently modified data members
|
||||
// ------------vvvvvvvvvvvvv-----------
|
||||
LRUHandleTable table_;
|
||||
|
||||
// Memory size for entries residing in the cache.
|
||||
size_t usage_;
|
||||
|
||||
// Memory size for entries residing only in the LRU list.
|
||||
size_t lru_usage_;
|
||||
|
||||
// mutex_ protects the following state.
|
||||
// We don't count mutex_ as the cache's internal state so semantically we
|
||||
// don't mind mutex_ invoking the non-const actions.
|
||||
mutable DMutex mutex_;
|
||||
};
|
||||
|
||||
class LRUCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#endif
|
||||
: public ShardedCache<LRUCacheShard> {
|
||||
public:
|
||||
LRUCache(size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata);
|
||||
const char* Name() const override { return "LRUCache"; }
|
||||
void* Value(Handle* handle) override;
|
||||
size_t GetCharge(Handle* handle) const override;
|
||||
DeleterFn GetDeleter(Handle* handle) const override;
|
||||
};
|
||||
} // namespace fast_lru_cache
|
||||
|
||||
std::shared_ptr<Cache> NewFastLRUCache(
|
||||
size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+352
-156
@@ -14,29 +14,28 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "cache/secondary_cache_adapter.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "port/lang.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace lru_cache {
|
||||
|
||||
LRUHandleTable::LRUHandleTable(int max_upper_hash_bits,
|
||||
MemoryAllocator* allocator)
|
||||
// A distinct pointer value for marking "dummy" cache entries
|
||||
void* const kDummyValueMarker = const_cast<char*>("kDummyValueMarker");
|
||||
|
||||
LRUHandleTable::LRUHandleTable(int max_upper_hash_bits)
|
||||
: length_bits_(/* historical starting size*/ 4),
|
||||
list_(new LRUHandle* [size_t{1} << length_bits_] {}),
|
||||
elems_(0),
|
||||
max_length_bits_(max_upper_hash_bits),
|
||||
allocator_(allocator) {}
|
||||
max_length_bits_(max_upper_hash_bits) {}
|
||||
|
||||
LRUHandleTable::~LRUHandleTable() {
|
||||
auto alloc = allocator_;
|
||||
ApplyToEntriesRange(
|
||||
[alloc](LRUHandle* h) {
|
||||
[](LRUHandle* h) {
|
||||
if (!h->HasRefs()) {
|
||||
h->Free(alloc);
|
||||
h->Free();
|
||||
}
|
||||
},
|
||||
0, size_t{1} << length_bits_);
|
||||
@@ -96,7 +95,7 @@ void LRUHandleTable::Resize() {
|
||||
std::unique_ptr<LRUHandle* []> new_list {
|
||||
new LRUHandle* [size_t{1} << new_length_bits] {}
|
||||
};
|
||||
[[maybe_unused]] uint32_t count = 0;
|
||||
uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < old_length; i++) {
|
||||
LRUHandle* h = list_[i];
|
||||
while (h != nullptr) {
|
||||
@@ -119,8 +118,7 @@ LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double low_pri_pool_ratio, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits,
|
||||
MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback)
|
||||
SecondaryCache* secondary_cache)
|
||||
: CacheShardBase(metadata_charge_policy),
|
||||
capacity_(0),
|
||||
high_pri_pool_usage_(0),
|
||||
@@ -130,11 +128,11 @@ LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
high_pri_pool_capacity_(0),
|
||||
low_pri_pool_ratio_(low_pri_pool_ratio),
|
||||
low_pri_pool_capacity_(0),
|
||||
table_(max_upper_hash_bits, allocator),
|
||||
table_(max_upper_hash_bits),
|
||||
usage_(0),
|
||||
lru_usage_(0),
|
||||
mutex_(use_adaptive_mutex),
|
||||
eviction_callback_(*eviction_callback) {
|
||||
secondary_cache_(secondary_cache) {
|
||||
// Make empty circular linked list.
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
@@ -161,14 +159,13 @@ void LRUCacheShard::EraseUnRefEntries() {
|
||||
}
|
||||
|
||||
for (auto entry : last_reference_list) {
|
||||
entry->Free(table_.GetAllocator());
|
||||
entry->Free();
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, Cache::ObjectPtr value,
|
||||
size_t charge,
|
||||
const Cache::CacheItemHelper* helper)>& callback,
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
size_t average_entries_per_lock, size_t* state) {
|
||||
// The state is essentially going to be the starting hash, which works
|
||||
// nicely even if we resize between calls because we use upper-most
|
||||
@@ -195,8 +192,11 @@ void LRUCacheShard::ApplyToSomeEntries(
|
||||
table_.ApplyToEntriesRange(
|
||||
[callback,
|
||||
metadata_charge_policy = metadata_charge_policy_](LRUHandle* h) {
|
||||
DeleterFn deleter = h->IsSecondaryCacheCompatible()
|
||||
? h->info_.helper->del_cb
|
||||
: h->info_.deleter;
|
||||
callback(h->key(), h->value, h->GetCharge(metadata_charge_policy),
|
||||
h->helper);
|
||||
deleter);
|
||||
},
|
||||
index_begin, index_end);
|
||||
}
|
||||
@@ -334,20 +334,16 @@ void LRUCacheShard::EvictFromLRU(size_t charge,
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::NotifyEvicted(
|
||||
const autovector<LRUHandle*>& evicted_handles) {
|
||||
MemoryAllocator* alloc = table_.GetAllocator();
|
||||
for (LRUHandle* entry : evicted_handles) {
|
||||
if (eviction_callback_ &&
|
||||
eviction_callback_(entry->key(),
|
||||
reinterpret_cast<Cache::Handle*>(entry),
|
||||
entry->HasHit())) {
|
||||
// Callback took ownership of obj; just free handle
|
||||
free(entry);
|
||||
} else {
|
||||
// Free the entries here outside of mutex for performance reasons.
|
||||
entry->Free(alloc);
|
||||
void LRUCacheShard::TryInsertIntoSecondaryCache(
|
||||
autovector<LRUHandle*> evicted_handles) {
|
||||
for (auto entry : evicted_handles) {
|
||||
if (secondary_cache_ && entry->IsSecondaryCacheCompatible() &&
|
||||
!entry->IsInSecondaryCache()) {
|
||||
secondary_cache_->Insert(entry->key(), entry->value, entry->info_.helper)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
// Free the entries here outside of mutex for performance reasons.
|
||||
entry->Free();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +357,7 @@ void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
}
|
||||
|
||||
NotifyEvicted(last_reference_list);
|
||||
TryInsertIntoSecondaryCache(last_reference_list);
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
@@ -369,7 +365,8 @@ void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle) {
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle,
|
||||
bool free_handle_on_fail) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
|
||||
@@ -388,9 +385,10 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle) {
|
||||
// into cache and get evicted immediately.
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
free(e);
|
||||
e = nullptr;
|
||||
*handle = nullptr;
|
||||
if (free_handle_on_fail) {
|
||||
free(e);
|
||||
*handle = nullptr;
|
||||
}
|
||||
s = Status::MemoryLimit("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
@@ -422,27 +420,192 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, LRUHandle** handle) {
|
||||
}
|
||||
}
|
||||
|
||||
NotifyEvicted(last_reference_list);
|
||||
TryInsertIntoSecondaryCache(last_reference_list);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
LRUHandle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
Cache::CreateContext* /*create_context*/,
|
||||
Cache::Priority /*priority*/,
|
||||
Statistics* /*stats*/) {
|
||||
DMutexLock l(mutex_);
|
||||
LRUHandle* e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external
|
||||
// references.
|
||||
LRU_Remove(e);
|
||||
void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
SecondaryCacheResultHandle* secondary_handle = e->sec_handle;
|
||||
|
||||
assert(secondary_handle->IsReady());
|
||||
// e is not thread-shared here; OK to modify "immutable" fields as well as
|
||||
// "mutable" (normally requiring mutex)
|
||||
e->SetIsPending(false);
|
||||
e->value = secondary_handle->Value();
|
||||
assert(e->total_charge == 0);
|
||||
size_t value_size = secondary_handle->Size();
|
||||
delete secondary_handle;
|
||||
|
||||
if (e->value) {
|
||||
e->CalcTotalCharge(value_size, metadata_charge_policy_);
|
||||
Status s;
|
||||
if (e->IsStandalone()) {
|
||||
assert(secondary_cache_ && secondary_cache_->SupportForceErase());
|
||||
|
||||
// Insert a dummy handle and return a standalone handle to caller.
|
||||
// Charge the standalone handle.
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
bool free_standalone_handle{false};
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty.
|
||||
EvictFromLRU(e->total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + e->total_charge) > capacity_ && strict_capacity_limit_) {
|
||||
free_standalone_handle = true;
|
||||
} else {
|
||||
usage_ += e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
TryInsertIntoSecondaryCache(last_reference_list);
|
||||
if (free_standalone_handle) {
|
||||
e->Unref();
|
||||
e->Free();
|
||||
e = nullptr;
|
||||
} else {
|
||||
PERF_COUNTER_ADD(block_cache_standalone_handle_count, 1);
|
||||
}
|
||||
|
||||
// Insert a dummy handle into the primary cache. This dummy handle is
|
||||
// not IsSecondaryCacheCompatible().
|
||||
// FIXME? This should not overwrite an existing non-dummy entry in the
|
||||
// rare case that one exists
|
||||
Cache::Priority priority =
|
||||
e->IsHighPri() ? Cache::Priority::HIGH : Cache::Priority::LOW;
|
||||
s = Insert(e->key(), e->hash, kDummyValueMarker, /*charge=*/0,
|
||||
/*deleter=*/nullptr, /*helper=*/nullptr, /*handle=*/nullptr,
|
||||
priority);
|
||||
} else {
|
||||
e->SetInCache(true);
|
||||
LRUHandle* handle = e;
|
||||
// This InsertItem() could fail if the cache is over capacity and
|
||||
// strict_capacity_limit_ is true. In such a case, we don't want
|
||||
// InsertItem() to free the handle, since the item is already in memory
|
||||
// and the caller will most likely just read it from disk if we erase it
|
||||
// here.
|
||||
s = InsertItem(e, &handle, /*free_handle_on_fail=*/false);
|
||||
if (s.ok()) {
|
||||
PERF_COUNTER_ADD(block_cache_real_handle_count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
// Item is in memory, but not accounted against the cache capacity.
|
||||
// When the handle is released, the item should get deleted.
|
||||
assert(!e->InCache());
|
||||
}
|
||||
} else {
|
||||
// Secondary cache lookup failed. The caller will take care of detecting
|
||||
// this and eventually releasing e.
|
||||
assert(!e->value);
|
||||
assert(!e->InCache());
|
||||
}
|
||||
}
|
||||
|
||||
LRUHandle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
const Cache::CreateCallback& create_cb,
|
||||
Cache::Priority priority, bool wait,
|
||||
Statistics* stats) {
|
||||
LRUHandle* e = nullptr;
|
||||
bool found_dummy_entry{false};
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (e->value == kDummyValueMarker) {
|
||||
// For a dummy handle, if it was retrieved from secondary cache,
|
||||
// it may still exist in secondary cache.
|
||||
// If the handle exists in secondary cache, the value should be
|
||||
// erased from sec cache and be inserted into primary cache.
|
||||
found_dummy_entry = true;
|
||||
// Let the dummy entry be overwritten
|
||||
e = nullptr;
|
||||
} else {
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external
|
||||
// references.
|
||||
LRU_Remove(e);
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If handle table lookup failed or the handle is a dummy one, allocate
|
||||
// a handle outside the mutex if we re going to lookup in the secondary cache.
|
||||
//
|
||||
// When a block is firstly Lookup from CompressedSecondaryCache, we just
|
||||
// insert a dummy block into the primary cache (charging the actual size of
|
||||
// the block) and don't erase the block from CompressedSecondaryCache. A
|
||||
// standalone handle is returned to the caller. Only if the block is hit
|
||||
// again, we erase it from CompressedSecondaryCache and add it into the
|
||||
// primary cache.
|
||||
if (!e && secondary_cache_ && helper && helper->saveto_cb) {
|
||||
// For objects from the secondary cache, we expect the caller to provide
|
||||
// a way to create/delete the primary cache object. The only case where
|
||||
// a deleter would not be required is for dummy entries inserted for
|
||||
// accounting purposes, which we won't demote to the secondary cache
|
||||
// anyway.
|
||||
assert(create_cb && helper->del_cb);
|
||||
bool is_in_sec_cache{false};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(key, create_cb, wait, found_dummy_entry,
|
||||
is_in_sec_cache);
|
||||
if (secondary_handle != nullptr) {
|
||||
e = static_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
|
||||
|
||||
e->m_flags = 0;
|
||||
e->im_flags = 0;
|
||||
e->SetSecondaryCacheCompatible(true);
|
||||
e->info_.helper = helper;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->value = nullptr;
|
||||
e->sec_handle = secondary_handle.release();
|
||||
e->total_charge = 0;
|
||||
e->Ref();
|
||||
e->SetIsInSecondaryCache(is_in_sec_cache);
|
||||
e->SetIsStandalone(secondary_cache_->SupportForceErase() &&
|
||||
!found_dummy_entry);
|
||||
|
||||
if (wait) {
|
||||
Promote(e);
|
||||
if (e) {
|
||||
if (!e->value) {
|
||||
// The secondary cache returned a handle, but the lookup failed.
|
||||
e->Unref();
|
||||
e->Free();
|
||||
e = nullptr;
|
||||
} else {
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If wait is false, we always return a handle and let the caller
|
||||
// release the handle after checking for success or failure.
|
||||
e->SetIsPending(true);
|
||||
// This may be slightly inaccurate, if the lookup eventually fails.
|
||||
// But the probability is very low.
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
}
|
||||
} else {
|
||||
// Caller will most likely overwrite the dummy entry with an Insert
|
||||
// after this Lookup fails
|
||||
assert(e == nullptr);
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
return e;
|
||||
}
|
||||
@@ -451,6 +614,8 @@ bool LRUCacheShard::Ref(LRUHandle* e) {
|
||||
DMutexLock l(mutex_);
|
||||
// To create another reference - entry must be already externally referenced.
|
||||
assert(e->HasRefs());
|
||||
// Pending handles are not for sharing
|
||||
assert(!e->IsPending());
|
||||
e->Ref();
|
||||
return true;
|
||||
}
|
||||
@@ -474,13 +639,14 @@ bool LRUCacheShard::Release(LRUHandle* e, bool /*useful*/,
|
||||
if (e == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool must_free;
|
||||
bool was_in_cache;
|
||||
bool last_reference = false;
|
||||
// Must Wait or WaitAll first on pending handles. Otherwise, would leak
|
||||
// a secondary cache handle.
|
||||
assert(!e->IsPending());
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
must_free = e->Unref();
|
||||
was_in_cache = e->InCache();
|
||||
if (must_free && was_in_cache) {
|
||||
last_reference = e->Unref();
|
||||
if (last_reference && e->InCache()) {
|
||||
// The item is still in cache, and nobody else holds a reference to it.
|
||||
if (usage_ > capacity_ || erase_if_last_ref) {
|
||||
// The LRU list must be empty since the cache is full.
|
||||
@@ -491,40 +657,28 @@ bool LRUCacheShard::Release(LRUHandle* e, bool /*useful*/,
|
||||
} else {
|
||||
// Put the item back on the LRU list, and don't free it.
|
||||
LRU_Insert(e);
|
||||
must_free = false;
|
||||
last_reference = false;
|
||||
}
|
||||
}
|
||||
// If about to be freed, then decrement the cache usage.
|
||||
if (must_free) {
|
||||
// If it was the last reference, then decrement the cache usage.
|
||||
if (last_reference) {
|
||||
assert(usage_ >= e->total_charge);
|
||||
usage_ -= e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entry here outside of mutex for performance reasons.
|
||||
if (must_free) {
|
||||
// Only call eviction callback if we're sure no one requested erasure
|
||||
// FIXME: disabled because of test churn
|
||||
if (false && was_in_cache && !erase_if_last_ref && eviction_callback_ &&
|
||||
eviction_callback_(e->key(), reinterpret_cast<Cache::Handle*>(e),
|
||||
e->HasHit())) {
|
||||
// Callback took ownership of obj; just free handle
|
||||
free(e);
|
||||
} else {
|
||||
e->Free(table_.GetAllocator());
|
||||
}
|
||||
if (last_reference) {
|
||||
e->Free();
|
||||
}
|
||||
return must_free;
|
||||
return last_reference;
|
||||
}
|
||||
|
||||
LRUHandle* LRUCacheShard::CreateHandle(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge) {
|
||||
assert(helper);
|
||||
// value == nullptr is reserved for indicating failure in SecondaryCache
|
||||
assert(!(helper->IsSecondaryCacheCompatible() && value == nullptr));
|
||||
|
||||
Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
const Cache::CacheItemHelper* helper,
|
||||
LRUHandle** handle, Cache::Priority priority) {
|
||||
// Allocate the memory here outside of the mutex.
|
||||
// If the cache is full, we'll have to release it.
|
||||
// It shouldn't happen very often though.
|
||||
@@ -534,58 +688,27 @@ LRUHandle* LRUCacheShard::CreateHandle(const Slice& key, uint32_t hash,
|
||||
e->value = value;
|
||||
e->m_flags = 0;
|
||||
e->im_flags = 0;
|
||||
e->helper = helper;
|
||||
if (helper) {
|
||||
// Use only one of the two parameters
|
||||
assert(deleter == nullptr);
|
||||
// value == nullptr is reserved for indicating failure for when secondary
|
||||
// cache compatible
|
||||
assert(value != nullptr);
|
||||
e->SetSecondaryCacheCompatible(true);
|
||||
e->info_.helper = helper;
|
||||
} else {
|
||||
e->info_.deleter = deleter;
|
||||
}
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->CalcTotalCharge(charge, metadata_charge_policy_);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
Status LRUCacheShard::Insert(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, LRUHandle** handle,
|
||||
Cache::Priority priority) {
|
||||
LRUHandle* e = CreateHandle(key, hash, value, helper, charge);
|
||||
e->SetPriority(priority);
|
||||
e->SetInCache(true);
|
||||
return InsertItem(e, handle);
|
||||
}
|
||||
|
||||
LRUHandle* LRUCacheShard::CreateStandalone(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge,
|
||||
bool allow_uncharged) {
|
||||
LRUHandle* e = CreateHandle(key, hash, value, helper, charge);
|
||||
e->SetIsStandalone(true);
|
||||
e->Ref();
|
||||
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
|
||||
EvictFromLRU(e->total_charge, &last_reference_list);
|
||||
|
||||
if (strict_capacity_limit_ && (usage_ + e->total_charge) > capacity_) {
|
||||
if (allow_uncharged) {
|
||||
e->total_charge = 0;
|
||||
} else {
|
||||
free(e);
|
||||
e = nullptr;
|
||||
}
|
||||
} else {
|
||||
usage_ += e->total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
NotifyEvicted(last_reference_list);
|
||||
return e;
|
||||
return InsertItem(e, handle, /* free_handle_on_fail */ true);
|
||||
}
|
||||
|
||||
void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
@@ -610,10 +733,20 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
// Free the entry here outside of mutex for performance reasons.
|
||||
// last_reference will only be true if e != nullptr.
|
||||
if (last_reference) {
|
||||
e->Free(table_.GetAllocator());
|
||||
e->Free();
|
||||
}
|
||||
}
|
||||
|
||||
bool LRUCacheShard::IsReady(LRUHandle* e) {
|
||||
bool ready = true;
|
||||
if (e->IsPending()) {
|
||||
assert(secondary_cache_);
|
||||
assert(e->sec_handle);
|
||||
ready = e->sec_handle->IsReady();
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetUsage() const {
|
||||
DMutexLock l(mutex_);
|
||||
return usage_;
|
||||
@@ -648,20 +781,30 @@ void LRUCacheShard::AppendPrintableOptions(std::string& str) const {
|
||||
str.append(buffer);
|
||||
}
|
||||
|
||||
LRUCache::LRUCache(const LRUCacheOptions& opts) : ShardedCache(opts) {
|
||||
LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit, double high_pri_pool_ratio,
|
||||
double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> allocator,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
std::shared_ptr<SecondaryCache> _secondary_cache)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(allocator)),
|
||||
secondary_cache_(std::move(_secondary_cache)) {
|
||||
size_t per_shard = GetPerShardCapacity();
|
||||
MemoryAllocator* alloc = memory_allocator();
|
||||
InitShards([&](LRUCacheShard* cs) {
|
||||
new (cs) LRUCacheShard(per_shard, opts.strict_capacity_limit,
|
||||
opts.high_pri_pool_ratio, opts.low_pri_pool_ratio,
|
||||
opts.use_adaptive_mutex, opts.metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - opts.num_shard_bits,
|
||||
alloc, &eviction_callback_);
|
||||
SecondaryCache* secondary_cache = secondary_cache_.get();
|
||||
InitShards([=](LRUCacheShard* cs) {
|
||||
new (cs) LRUCacheShard(
|
||||
per_shard, strict_capacity_limit, high_pri_pool_ratio,
|
||||
low_pri_pool_ratio, use_adaptive_mutex, metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - num_shard_bits, secondary_cache);
|
||||
});
|
||||
}
|
||||
|
||||
Cache::ObjectPtr LRUCache::Value(Handle* handle) {
|
||||
void* LRUCache::Value(Handle* handle) {
|
||||
auto h = reinterpret_cast<const LRUHandle*>(handle);
|
||||
assert(!h->IsPending() || h->value == nullptr);
|
||||
assert(h->value != kDummyValueMarker);
|
||||
return h->value;
|
||||
}
|
||||
|
||||
@@ -670,10 +813,13 @@ size_t LRUCache::GetCharge(Handle* handle) const {
|
||||
GetShard(0).metadata_charge_policy_);
|
||||
}
|
||||
|
||||
const Cache::CacheItemHelper* LRUCache::GetCacheItemHelper(
|
||||
Handle* handle) const {
|
||||
Cache::DeleterFn LRUCache::GetDeleter(Handle* handle) const {
|
||||
auto h = reinterpret_cast<const LRUHandle*>(handle);
|
||||
return h->helper;
|
||||
if (h->IsSecondaryCacheCompatible()) {
|
||||
return h->info_.helper->del_cb;
|
||||
} else {
|
||||
return h->info_.deleter;
|
||||
}
|
||||
}
|
||||
|
||||
size_t LRUCache::TEST_GetLRUSize() {
|
||||
@@ -684,9 +830,51 @@ double LRUCache::GetHighPriPoolRatio() {
|
||||
return GetShard(0).GetHighPriPoolRatio();
|
||||
}
|
||||
|
||||
void LRUCache::WaitAll(std::vector<Handle*>& handles) {
|
||||
if (secondary_cache_) {
|
||||
std::vector<SecondaryCacheResultHandle*> sec_handles;
|
||||
sec_handles.reserve(handles.size());
|
||||
for (Handle* handle : handles) {
|
||||
if (!handle) {
|
||||
continue;
|
||||
}
|
||||
LRUHandle* lru_handle = reinterpret_cast<LRUHandle*>(handle);
|
||||
if (!lru_handle->IsPending()) {
|
||||
continue;
|
||||
}
|
||||
sec_handles.emplace_back(lru_handle->sec_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(sec_handles);
|
||||
for (Handle* handle : handles) {
|
||||
if (!handle) {
|
||||
continue;
|
||||
}
|
||||
LRUHandle* lru_handle = reinterpret_cast<LRUHandle*>(handle);
|
||||
if (!lru_handle->IsPending()) {
|
||||
continue;
|
||||
}
|
||||
GetShard(lru_handle->hash).Promote(lru_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCache::AppendPrintableOptions(std::string& str) const {
|
||||
ShardedCache::AppendPrintableOptions(str); // options from shard
|
||||
if (secondary_cache_) {
|
||||
str.append(" secondary_cache:\n");
|
||||
str.append(secondary_cache_->GetPrintableOptions());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lru_cache
|
||||
|
||||
std::shared_ptr<Cache> LRUCacheOptions::MakeSharedCache() const {
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache,
|
||||
double low_pri_pool_ratio) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // The cache cannot be sharded into too many fine pieces.
|
||||
}
|
||||
@@ -702,24 +890,32 @@ std::shared_ptr<Cache> LRUCacheOptions::MakeSharedCache() const {
|
||||
// Invalid high_pri_pool_ratio and low_pri_pool_ratio combination
|
||||
return nullptr;
|
||||
}
|
||||
// For sanitized options
|
||||
LRUCacheOptions opts = *this;
|
||||
if (opts.num_shard_bits < 0) {
|
||||
opts.num_shard_bits = GetDefaultCacheShardBits(capacity);
|
||||
if (num_shard_bits < 0) {
|
||||
num_shard_bits = GetDefaultCacheShardBits(capacity);
|
||||
}
|
||||
std::shared_ptr<Cache> cache = std::make_shared<LRUCache>(opts);
|
||||
if (secondary_cache) {
|
||||
cache = std::make_shared<CacheWithSecondaryAdapter>(cache, secondary_cache);
|
||||
}
|
||||
return cache;
|
||||
return std::make_shared<LRUCache>(
|
||||
capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
|
||||
low_pri_pool_ratio, std::move(memory_allocator), use_adaptive_mutex,
|
||||
metadata_charge_policy, secondary_cache);
|
||||
}
|
||||
|
||||
std::shared_ptr<RowCache> LRUCacheOptions::MakeSharedRowCache() const {
|
||||
if (secondary_cache) {
|
||||
// Not allowed for a RowCache
|
||||
return nullptr;
|
||||
}
|
||||
// Works while RowCache is an alias for Cache
|
||||
return MakeSharedCache();
|
||||
std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit,
|
||||
cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy,
|
||||
cache_opts.secondary_cache, cache_opts.low_pri_pool_ratio);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
double low_pri_pool_ratio) {
|
||||
return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, nullptr, low_pri_pool_ratio);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+129
-50
@@ -13,9 +13,9 @@
|
||||
|
||||
#include "cache/sharded_cache.h"
|
||||
#include "port/lang.h"
|
||||
#include "port/likely.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
@@ -48,9 +48,19 @@ namespace lru_cache {
|
||||
// While refs > 0, public properties like value and deleter must not change.
|
||||
|
||||
struct LRUHandle {
|
||||
Cache::ObjectPtr value;
|
||||
const Cache::CacheItemHelper* helper;
|
||||
LRUHandle* next_hash;
|
||||
void* value;
|
||||
union Info {
|
||||
Info() {}
|
||||
~Info() {}
|
||||
Cache::DeleterFn deleter;
|
||||
const Cache::CacheItemHelper* helper;
|
||||
} info_;
|
||||
// An entry is not added to the LRUHandleTable until the secondary cache
|
||||
// lookup is complete, so its safe to have this union.
|
||||
union {
|
||||
LRUHandle* next_hash;
|
||||
SecondaryCacheResultHandle* sec_handle;
|
||||
};
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t total_charge; // TODO(opt): Only allow uint32_t?
|
||||
@@ -83,8 +93,14 @@ struct LRUHandle {
|
||||
IM_IS_HIGH_PRI = (1 << 0),
|
||||
// Whether this entry is low priority entry.
|
||||
IM_IS_LOW_PRI = (1 << 1),
|
||||
// Can this be inserted into the secondary cache.
|
||||
IM_IS_SECONDARY_CACHE_COMPATIBLE = (1 << 2),
|
||||
// Is the handle still being read from a lower tier.
|
||||
IM_IS_PENDING = (1 << 3),
|
||||
// Whether this handle is still in a lower tier
|
||||
IM_IS_IN_SECONDARY_CACHE = (1 << 4),
|
||||
// Marks result handles that should not be inserted into cache
|
||||
IM_IS_STANDALONE = (1 << 2),
|
||||
IM_IS_STANDALONE = (1 << 5),
|
||||
};
|
||||
|
||||
// Beginning of the key (MUST BE THE LAST FIELD IN THIS STRUCT!)
|
||||
@@ -114,6 +130,13 @@ struct LRUHandle {
|
||||
bool IsLowPri() const { return im_flags & IM_IS_LOW_PRI; }
|
||||
bool InLowPriPool() const { return m_flags & M_IN_LOW_PRI_POOL; }
|
||||
bool HasHit() const { return m_flags & M_HAS_HIT; }
|
||||
bool IsSecondaryCacheCompatible() const {
|
||||
return im_flags & IM_IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
}
|
||||
bool IsPending() const { return im_flags & IM_IS_PENDING; }
|
||||
bool IsInSecondaryCache() const {
|
||||
return im_flags & IM_IS_IN_SECONDARY_CACHE;
|
||||
}
|
||||
bool IsStandalone() const { return im_flags & IM_IS_STANDALONE; }
|
||||
|
||||
void SetInCache(bool in_cache) {
|
||||
@@ -155,6 +178,30 @@ struct LRUHandle {
|
||||
|
||||
void SetHit() { m_flags |= M_HAS_HIT; }
|
||||
|
||||
void SetSecondaryCacheCompatible(bool compat) {
|
||||
if (compat) {
|
||||
im_flags |= IM_IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
} else {
|
||||
im_flags &= ~IM_IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
}
|
||||
}
|
||||
|
||||
void SetIsPending(bool pending) {
|
||||
if (pending) {
|
||||
im_flags |= IM_IS_PENDING;
|
||||
} else {
|
||||
im_flags &= ~IM_IS_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
void SetIsInSecondaryCache(bool is_in_secondary_cache) {
|
||||
if (is_in_secondary_cache) {
|
||||
im_flags |= IM_IS_IN_SECONDARY_CACHE;
|
||||
} else {
|
||||
im_flags &= ~IM_IS_IN_SECONDARY_CACHE;
|
||||
}
|
||||
}
|
||||
|
||||
void SetIsStandalone(bool is_standalone) {
|
||||
if (is_standalone) {
|
||||
im_flags |= IM_IS_STANDALONE;
|
||||
@@ -163,11 +210,22 @@ struct LRUHandle {
|
||||
}
|
||||
}
|
||||
|
||||
void Free(MemoryAllocator* allocator) {
|
||||
void Free() {
|
||||
assert(refs == 0);
|
||||
assert(helper);
|
||||
if (helper->del_cb) {
|
||||
helper->del_cb(value, allocator);
|
||||
|
||||
if (!IsSecondaryCacheCompatible() && info_.deleter) {
|
||||
(*info_.deleter)(key(), value);
|
||||
} else if (IsSecondaryCacheCompatible()) {
|
||||
if (IsPending()) {
|
||||
assert(sec_handle != nullptr);
|
||||
SecondaryCacheResultHandle* tmp_sec_handle = sec_handle;
|
||||
tmp_sec_handle->Wait();
|
||||
value = tmp_sec_handle->Value();
|
||||
delete tmp_sec_handle;
|
||||
}
|
||||
if (value) {
|
||||
(*info_.helper->del_cb)(key(), value);
|
||||
}
|
||||
}
|
||||
|
||||
free(this);
|
||||
@@ -209,7 +267,7 @@ struct LRUHandle {
|
||||
// 4.4.3's builtin hashtable.
|
||||
class LRUHandleTable {
|
||||
public:
|
||||
explicit LRUHandleTable(int max_upper_hash_bits, MemoryAllocator* allocator);
|
||||
explicit LRUHandleTable(int max_upper_hash_bits);
|
||||
~LRUHandleTable();
|
||||
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash);
|
||||
@@ -233,8 +291,6 @@ class LRUHandleTable {
|
||||
|
||||
size_t GetOccupancyCount() const { return elems_; }
|
||||
|
||||
MemoryAllocator* GetAllocator() const { return allocator_; }
|
||||
|
||||
private:
|
||||
// Return a pointer to slot that points to a cache entry that
|
||||
// matches key/hash. If there is no such cache entry, return a
|
||||
@@ -256,22 +312,16 @@ class LRUHandleTable {
|
||||
|
||||
// Set from max_upper_hash_bits (see constructor).
|
||||
const int max_length_bits_;
|
||||
|
||||
// From Cache, needed for delete
|
||||
MemoryAllocator* const allocator_;
|
||||
};
|
||||
|
||||
// A single shard of sharded cache.
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
public:
|
||||
// NOTE: the eviction_callback ptr is saved, as is it assumed to be kept
|
||||
// alive in Cache.
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits, MemoryAllocator* allocator,
|
||||
const Cache::EvictionCallback* eviction_callback);
|
||||
int max_upper_hash_bits, SecondaryCache* secondary_cache);
|
||||
|
||||
public: // Type definitions expected as parameter to ShardedCache
|
||||
using HandleImpl = LRUHandle;
|
||||
@@ -279,8 +329,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
using HashCref = uint32_t;
|
||||
|
||||
public: // Function definitions expected as parameter to ShardedCache
|
||||
static inline HashVal ComputeHash(const Slice& key, uint32_t seed) {
|
||||
return Lower32of64(GetSliceNPHash64(key, seed));
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
return Lower32of64(GetSliceNPHash64(key));
|
||||
}
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -298,21 +348,29 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
void SetLowPriorityPoolRatio(double low_pri_pool_ratio);
|
||||
|
||||
// Like Cache methods, but with an extra "hash" parameter.
|
||||
Status Insert(const Slice& key, uint32_t hash, Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
LRUHandle** handle, Cache::Priority priority);
|
||||
|
||||
LRUHandle* CreateStandalone(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr obj,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
size_t charge, bool allow_uncharged);
|
||||
|
||||
inline Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge, Cache::DeleterFn deleter,
|
||||
LRUHandle** handle, Cache::Priority priority) {
|
||||
return Insert(key, hash, value, charge, deleter, nullptr, handle, priority);
|
||||
}
|
||||
inline Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
LRUHandle** handle, Cache::Priority priority) {
|
||||
assert(helper);
|
||||
return Insert(key, hash, value, charge, nullptr, helper, handle, priority);
|
||||
}
|
||||
// If helper_cb is null, the values of the following arguments don't matter.
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context,
|
||||
Cache::Priority priority, Statistics* stats);
|
||||
|
||||
const Cache::CreateCallback& create_cb,
|
||||
Cache::Priority priority, bool wait, Statistics* stats);
|
||||
inline LRUHandle* Lookup(const Slice& key, uint32_t hash) {
|
||||
return Lookup(key, hash, nullptr, nullptr, Cache::Priority::LOW, true,
|
||||
nullptr);
|
||||
}
|
||||
bool Release(LRUHandle* handle, bool useful, bool erase_if_last_ref);
|
||||
bool IsReady(LRUHandle* /*handle*/);
|
||||
void Wait(LRUHandle* /*handle*/) {}
|
||||
bool Ref(LRUHandle* handle);
|
||||
void Erase(const Slice& key, uint32_t hash);
|
||||
|
||||
@@ -326,9 +384,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
size_t GetTableAddressCount() const;
|
||||
|
||||
void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, Cache::ObjectPtr value,
|
||||
size_t charge,
|
||||
const Cache::CacheItemHelper* helper)>& callback,
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
size_t average_entries_per_lock, size_t* state);
|
||||
|
||||
void EraseUnRefEntries();
|
||||
@@ -352,10 +409,23 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
private:
|
||||
friend class LRUCache;
|
||||
// Insert an item into the hash table and, if handle is null, insert into
|
||||
// the LRU list. Older items are evicted as necessary. Frees `item` on
|
||||
// non-OK status.
|
||||
Status InsertItem(LRUHandle* item, LRUHandle** handle);
|
||||
|
||||
// the LRU list. Older items are evicted as necessary. If the cache is full
|
||||
// and free_handle_on_fail is true, the item is deleted and handle is set to
|
||||
// nullptr.
|
||||
Status InsertItem(LRUHandle* item, LRUHandle** handle,
|
||||
bool free_handle_on_fail);
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
DeleterFn deleter, const Cache::CacheItemHelper* helper,
|
||||
LRUHandle** handle, Cache::Priority priority);
|
||||
// Promote an item looked up from the secondary cache to the LRU cache.
|
||||
// The item may be still in the secondary cache.
|
||||
// It is only inserted into the hash table and not the LRU list, and only
|
||||
// if the cache is not at full capacity, as is the case during Insert. The
|
||||
// caller should hold a reference on the LRUHandle. When the caller releases
|
||||
// the last reference, the item is added to the LRU list.
|
||||
// The item is promoted to the high pri or low pri pool as specified by the
|
||||
// caller in Lookup.
|
||||
void Promote(LRUHandle* e);
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Insert(LRUHandle* e);
|
||||
|
||||
@@ -369,11 +439,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
// holding the mutex_.
|
||||
void EvictFromLRU(size_t charge, autovector<LRUHandle*>* deleted);
|
||||
|
||||
void NotifyEvicted(const autovector<LRUHandle*>& evicted_handles);
|
||||
|
||||
LRUHandle* CreateHandle(const Slice& key, uint32_t hash,
|
||||
Cache::ObjectPtr value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge);
|
||||
// Try to insert the evicted handles into the secondary cache.
|
||||
void TryInsertIntoSecondaryCache(autovector<LRUHandle*> evicted_handles);
|
||||
|
||||
// Initialized before use.
|
||||
size_t capacity_;
|
||||
@@ -436,8 +503,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShardBase {
|
||||
// don't mind mutex_ invoking the non-const actions.
|
||||
mutable DMutex mutex_;
|
||||
|
||||
// A reference to Cache::eviction_callback_
|
||||
const Cache::EvictionCallback& eviction_callback_;
|
||||
// Owned by LRUCache
|
||||
SecondaryCache* secondary_cache_;
|
||||
};
|
||||
|
||||
class LRUCache
|
||||
@@ -446,16 +513,28 @@ class LRUCache
|
||||
#endif
|
||||
: public ShardedCache<LRUCacheShard> {
|
||||
public:
|
||||
explicit LRUCache(const LRUCacheOptions& opts);
|
||||
LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache = nullptr);
|
||||
const char* Name() const override { return "LRUCache"; }
|
||||
ObjectPtr Value(Handle* handle) override;
|
||||
void* Value(Handle* handle) override;
|
||||
size_t GetCharge(Handle* handle) const override;
|
||||
const CacheItemHelper* GetCacheItemHelper(Handle* handle) const override;
|
||||
DeleterFn GetDeleter(Handle* handle) const override;
|
||||
void WaitAll(std::vector<Handle*>& handles) override;
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only.
|
||||
size_t TEST_GetLRUSize();
|
||||
// Retrieves high pri pool ratio.
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
void AppendPrintableOptions(std::string& str) const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
} // namespace lru_cache
|
||||
|
||||
Vendored
+771
-707
File diff suppressed because it is too large.
Load diff
Vendored
-45
@@ -1,45 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
void NoopDelete(Cache::ObjectPtr, MemoryAllocator*) {}
|
||||
|
||||
size_t SliceSize(Cache::ObjectPtr obj) {
|
||||
return static_cast<Slice*>(obj)->size();
|
||||
}
|
||||
|
||||
Status SliceSaveTo(Cache::ObjectPtr from_obj, size_t from_offset, size_t length,
|
||||
char* out) {
|
||||
const Slice& slice = *static_cast<Slice*>(from_obj);
|
||||
std::memcpy(out, slice.data() + from_offset, length);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status FailCreate(const Slice&, Cache::CreateContext*, MemoryAllocator*,
|
||||
Cache::ObjectPtr*, size_t*) {
|
||||
return Status::NotSupported("Only for dumping data into SecondaryCache");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status SecondaryCache::InsertSaved(const Slice& key, const Slice& saved) {
|
||||
static Cache::CacheItemHelper helper_no_secondary{CacheEntryRole::kMisc,
|
||||
&NoopDelete};
|
||||
static Cache::CacheItemHelper helper{
|
||||
CacheEntryRole::kMisc, &NoopDelete, &SliceSize,
|
||||
&SliceSaveTo, &FailCreate, &helper_no_secondary};
|
||||
// NOTE: depends on Insert() being synchronous, not keeping pointer `&saved`
|
||||
return Insert(key, const_cast<Slice*>(&saved), &helper,
|
||||
/*force_insert=*/true);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
-446
@@ -1,446 +0,0 @@
|
||||
// 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 "cache/secondary_cache_adapter.h"
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/cast_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
// A distinct pointer value for marking "dummy" cache entries
|
||||
struct Dummy {
|
||||
char val[7] = "kDummy";
|
||||
};
|
||||
const Dummy kDummy{};
|
||||
Cache::ObjectPtr const kDummyObj = const_cast<Dummy*>(&kDummy);
|
||||
} // namespace
|
||||
|
||||
// When CacheWithSecondaryAdapter is constructed with the distribute_cache_res
|
||||
// parameter set to true, it manages the entire memory budget across the
|
||||
// primary and secondary cache. The secondary cache is assumed to be in
|
||||
// memory, such as the CompressedSecondaryCache. When a placeholder entry
|
||||
// is inserted by a CacheReservationManager instance to reserve memory,
|
||||
// the CacheWithSecondaryAdapter ensures that the reservation is distributed
|
||||
// proportionally across the primary/secondary caches.
|
||||
//
|
||||
// The primary block cache is initially sized to the sum of the primary cache
|
||||
// budget + teh secondary cache budget, as follows -
|
||||
// |--------- Primary Cache Configured Capacity -----------|
|
||||
// |---Secondary Cache Budget----|----Primary Cache Budget-----|
|
||||
//
|
||||
// A ConcurrentCacheReservationManager member in the CacheWithSecondaryAdapter,
|
||||
// pri_cache_res_,
|
||||
// is used to help with tracking the distribution of memory reservations.
|
||||
// Initially, it accounts for the entire secondary cache budget as a
|
||||
// reservation against the primary cache. This shrinks the usable capacity of
|
||||
// the primary cache to the budget that the user originally desired.
|
||||
//
|
||||
// |--Reservation for Sec Cache--|-Pri Cache Usable Capacity---|
|
||||
//
|
||||
// When a reservation placeholder is inserted into the adapter, it is inserted
|
||||
// directly into the primary cache. This means the entire charge of the
|
||||
// placeholder is counted against the primary cache. To compensate and count
|
||||
// a portion of it against the secondary cache, the secondary cache Deflate()
|
||||
// method is called to shrink it. Since the Deflate() causes the secondary
|
||||
// actual usage to shrink, it is refelcted here by releasing an equal amount
|
||||
// from the pri_cache_res_ reservation. The Deflate() in the secondary cache
|
||||
// can be, but is not required to be, implemented using its own cache
|
||||
// reservation manager.
|
||||
//
|
||||
// For example, if the pri/sec ratio is 70/30, and the combined capacity is
|
||||
// 100MB, the intermediate and final state after inserting a reservation
|
||||
// placeholder for 10MB would be as follows -
|
||||
//
|
||||
// |-Reservation for Sec Cache-|-Pri Cache Usable Capacity-|---R---|
|
||||
// 1. After inserting the placeholder in primary
|
||||
// |------- 30MB -------------|------- 60MB -------------|-10MB--|
|
||||
// 2. After deflating the secondary and adjusting the reservation for
|
||||
// secondary against the primary
|
||||
// |------- 27MB -------------|------- 63MB -------------|-10MB--|
|
||||
//
|
||||
// Likewise, when the user inserted placeholder is released, the secondary
|
||||
// cache Inflate() method is called to grow it, and the pri_cache_res_
|
||||
// reservation is increased by an equal amount.
|
||||
//
|
||||
// Another way of implementing this would have been to simply split the user
|
||||
// reservation into primary and seconary components. However, this would
|
||||
// require allocating a structure to track the associated secondary cache
|
||||
// reservation, which adds some complexity and overhead.
|
||||
//
|
||||
CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
|
||||
std::shared_ptr<Cache> target,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache,
|
||||
TieredAdmissionPolicy adm_policy, bool distribute_cache_res)
|
||||
: CacheWrapper(std::move(target)),
|
||||
secondary_cache_(std::move(secondary_cache)),
|
||||
adm_policy_(adm_policy),
|
||||
distribute_cache_res_(distribute_cache_res) {
|
||||
target_->SetEvictionCallback(
|
||||
[this](const Slice& key, Handle* handle, bool was_hit) {
|
||||
return EvictionHandler(key, handle, was_hit);
|
||||
});
|
||||
if (distribute_cache_res_) {
|
||||
size_t sec_capacity = 0;
|
||||
pri_cache_res_ = std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
target_));
|
||||
Status s = secondary_cache_->GetCapacity(sec_capacity);
|
||||
assert(s.ok());
|
||||
// Initially, the primary cache is sized to uncompressed cache budget plsu
|
||||
// compressed secondary cache budget. The secondary cache budget is then
|
||||
// taken away from the primary cache through cache reservations. Later,
|
||||
// when a placeholder entry is inserted by the caller, its inserted
|
||||
// into the primary cache and the portion that should be assigned to the
|
||||
// secondary cache is freed from the reservation.
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_capacity);
|
||||
assert(s.ok());
|
||||
sec_cache_res_ratio_ = (double)sec_capacity / target_->GetCapacity();
|
||||
}
|
||||
}
|
||||
|
||||
CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
|
||||
// `*this` will be destroyed before `*target_`, so we have to prevent
|
||||
// use after free
|
||||
target_->SetEvictionCallback({});
|
||||
#ifndef NDEBUG
|
||||
if (distribute_cache_res_) {
|
||||
size_t sec_capacity = 0;
|
||||
Status s = secondary_cache_->GetCapacity(sec_capacity);
|
||||
assert(s.ok());
|
||||
assert(pri_cache_res_->GetTotalReservedCacheSize() == sec_capacity);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
bool CacheWithSecondaryAdapter::EvictionHandler(const Slice& key,
|
||||
Handle* handle, bool was_hit) {
|
||||
auto helper = GetCacheItemHelper(handle);
|
||||
if (helper->IsSecondaryCacheCompatible()) {
|
||||
auto obj = target_->Value(handle);
|
||||
// Ignore dummy entry
|
||||
if (obj != kDummyObj) {
|
||||
bool hit = false;
|
||||
if (adm_policy_ == TieredAdmissionPolicy::kAdmPolicyAllowCacheHits) {
|
||||
hit = was_hit;
|
||||
}
|
||||
// Spill into secondary cache.
|
||||
secondary_cache_->Insert(key, obj, helper, hit).PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
// Never takes ownership of obj
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CacheWithSecondaryAdapter::ProcessDummyResult(Cache::Handle** handle,
|
||||
bool erase) {
|
||||
if (*handle && target_->Value(*handle) == kDummyObj) {
|
||||
target_->Release(*handle, erase);
|
||||
*handle = nullptr;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void CacheWithSecondaryAdapter::CleanupCacheObject(
|
||||
ObjectPtr obj, const CacheItemHelper* helper) {
|
||||
if (helper->del_cb) {
|
||||
helper->del_cb(obj, memory_allocator());
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Handle* CacheWithSecondaryAdapter::Promote(
|
||||
std::unique_ptr<SecondaryCacheResultHandle>&& secondary_handle,
|
||||
const Slice& key, const CacheItemHelper* helper, Priority priority,
|
||||
Statistics* stats, bool found_dummy_entry, bool kept_in_sec_cache) {
|
||||
assert(secondary_handle->IsReady());
|
||||
|
||||
ObjectPtr obj = secondary_handle->Value();
|
||||
if (!obj) {
|
||||
// Nothing found.
|
||||
return nullptr;
|
||||
}
|
||||
// Found something.
|
||||
switch (helper->role) {
|
||||
case CacheEntryRole::kFilterBlock:
|
||||
RecordTick(stats, SECONDARY_CACHE_FILTER_HITS);
|
||||
break;
|
||||
case CacheEntryRole::kIndexBlock:
|
||||
RecordTick(stats, SECONDARY_CACHE_INDEX_HITS);
|
||||
break;
|
||||
case CacheEntryRole::kDataBlock:
|
||||
RecordTick(stats, SECONDARY_CACHE_DATA_HITS);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
|
||||
// Note: SecondaryCache::Size() is really charge (from the CreateCallback)
|
||||
size_t charge = secondary_handle->Size();
|
||||
Handle* result = nullptr;
|
||||
// Insert into primary cache, possibly as a standalone+dummy entries.
|
||||
if (secondary_cache_->SupportForceErase() && !found_dummy_entry) {
|
||||
// Create standalone and insert dummy
|
||||
// Allow standalone to be created even if cache is full, to avoid
|
||||
// reading the entry from storage.
|
||||
result =
|
||||
CreateStandalone(key, obj, helper, charge, /*allow_uncharged*/ true);
|
||||
assert(result);
|
||||
PERF_COUNTER_ADD(block_cache_standalone_handle_count, 1);
|
||||
|
||||
// Insert dummy to record recent use
|
||||
// TODO: try to avoid case where inserting this dummy could overwrite a
|
||||
// regular entry
|
||||
Status s = Insert(key, kDummyObj, &kNoopCacheItemHelper, /*charge=*/0,
|
||||
/*handle=*/nullptr, priority);
|
||||
s.PermitUncheckedError();
|
||||
// Nothing to do or clean up on dummy insertion failure
|
||||
} else {
|
||||
// Insert regular entry into primary cache.
|
||||
// Don't allow it to spill into secondary cache again if it was kept there.
|
||||
Status s = Insert(
|
||||
key, obj, kept_in_sec_cache ? helper->without_secondary_compat : helper,
|
||||
charge, &result, priority);
|
||||
if (s.ok()) {
|
||||
assert(result);
|
||||
PERF_COUNTER_ADD(block_cache_real_handle_count, 1);
|
||||
} else {
|
||||
// Create standalone result instead, even if cache is full, to avoid
|
||||
// reading the entry from storage.
|
||||
result =
|
||||
CreateStandalone(key, obj, helper, charge, /*allow_uncharged*/ true);
|
||||
assert(result);
|
||||
PERF_COUNTER_ADD(block_cache_standalone_handle_count, 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Status CacheWithSecondaryAdapter::Insert(const Slice& key, ObjectPtr value,
|
||||
const CacheItemHelper* helper,
|
||||
size_t charge, Handle** handle,
|
||||
Priority priority) {
|
||||
Status s = target_->Insert(key, value, helper, charge, handle, priority);
|
||||
if (s.ok() && value == nullptr && distribute_cache_res_) {
|
||||
size_t sec_charge = static_cast<size_t>(charge * (sec_cache_res_ratio_));
|
||||
s = secondary_cache_->Deflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge, /*increase=*/false);
|
||||
assert(s.ok());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Cache::Handle* CacheWithSecondaryAdapter::Lookup(const Slice& key,
|
||||
const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority,
|
||||
Statistics* stats) {
|
||||
// NOTE: we could just StartAsyncLookup() and Wait(), but this should be a bit
|
||||
// more efficient
|
||||
Handle* result =
|
||||
target_->Lookup(key, helper, create_context, priority, stats);
|
||||
bool secondary_compatible = helper && helper->IsSecondaryCacheCompatible();
|
||||
bool found_dummy_entry =
|
||||
ProcessDummyResult(&result, /*erase=*/secondary_compatible);
|
||||
if (!result && secondary_compatible) {
|
||||
// Try our secondary cache
|
||||
bool kept_in_sec_cache = false;
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(key, helper, create_context, /*wait*/ true,
|
||||
found_dummy_entry, /*out*/ kept_in_sec_cache);
|
||||
if (secondary_handle) {
|
||||
result = Promote(std::move(secondary_handle), key, helper, priority,
|
||||
stats, found_dummy_entry, kept_in_sec_cache);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CacheWithSecondaryAdapter::Release(Handle* handle,
|
||||
bool erase_if_last_ref) {
|
||||
if (erase_if_last_ref) {
|
||||
ObjectPtr v = target_->Value(handle);
|
||||
if (v == nullptr && distribute_cache_res_) {
|
||||
size_t charge = target_->GetCharge(handle);
|
||||
size_t sec_charge = static_cast<size_t>(charge * (sec_cache_res_ratio_));
|
||||
Status s = secondary_cache_->Inflate(sec_charge);
|
||||
assert(s.ok());
|
||||
s = pri_cache_res_->UpdateCacheReservation(sec_charge, /*increase=*/true);
|
||||
assert(s.ok());
|
||||
}
|
||||
}
|
||||
return target_->Release(handle, erase_if_last_ref);
|
||||
}
|
||||
|
||||
Cache::ObjectPtr CacheWithSecondaryAdapter::Value(Handle* handle) {
|
||||
ObjectPtr v = target_->Value(handle);
|
||||
// TODO with stacked secondaries: might fail in EvictionHandler
|
||||
assert(v != kDummyObj);
|
||||
return v;
|
||||
}
|
||||
|
||||
void CacheWithSecondaryAdapter::StartAsyncLookupOnMySecondary(
|
||||
AsyncLookupHandle& async_handle) {
|
||||
assert(!async_handle.IsPending());
|
||||
assert(async_handle.result_handle == nullptr);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(async_handle.key, async_handle.helper,
|
||||
async_handle.create_context, /*wait*/ false,
|
||||
async_handle.found_dummy_entry,
|
||||
/*out*/ async_handle.kept_in_sec_cache);
|
||||
if (secondary_handle) {
|
||||
// TODO with stacked secondaries: Check & process if already ready?
|
||||
async_handle.pending_handle = secondary_handle.release();
|
||||
async_handle.pending_cache = secondary_cache_.get();
|
||||
}
|
||||
}
|
||||
|
||||
void CacheWithSecondaryAdapter::StartAsyncLookup(
|
||||
AsyncLookupHandle& async_handle) {
|
||||
target_->StartAsyncLookup(async_handle);
|
||||
if (!async_handle.IsPending()) {
|
||||
bool secondary_compatible =
|
||||
async_handle.helper &&
|
||||
async_handle.helper->IsSecondaryCacheCompatible();
|
||||
async_handle.found_dummy_entry |= ProcessDummyResult(
|
||||
&async_handle.result_handle, /*erase=*/secondary_compatible);
|
||||
|
||||
if (async_handle.Result() == nullptr && secondary_compatible) {
|
||||
// Not found and not pending on another secondary cache
|
||||
StartAsyncLookupOnMySecondary(async_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CacheWithSecondaryAdapter::WaitAll(AsyncLookupHandle* async_handles,
|
||||
size_t count) {
|
||||
if (count == 0) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
// Requests that are pending on *my* secondary cache, at the start of this
|
||||
// function
|
||||
std::vector<AsyncLookupHandle*> my_pending;
|
||||
// Requests that are pending on an "inner" secondary cache (managed somewhere
|
||||
// under target_), as of the start of this function
|
||||
std::vector<AsyncLookupHandle*> inner_pending;
|
||||
|
||||
// Initial accounting of pending handles, excluding those already handled
|
||||
// by "outer" secondary caches. (See cur->pending_cache = nullptr.)
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
AsyncLookupHandle* cur = async_handles + i;
|
||||
if (cur->pending_cache) {
|
||||
assert(cur->IsPending());
|
||||
assert(cur->helper);
|
||||
assert(cur->helper->IsSecondaryCacheCompatible());
|
||||
if (cur->pending_cache == secondary_cache_.get()) {
|
||||
my_pending.push_back(cur);
|
||||
// Mark as "to be handled by this caller"
|
||||
cur->pending_cache = nullptr;
|
||||
} else {
|
||||
// Remember as potentially needing a lookup in my secondary
|
||||
inner_pending.push_back(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait on inner-most cache lookups first
|
||||
// TODO with stacked secondaries: because we are not using proper
|
||||
// async/await constructs here yet, there is a false synchronization point
|
||||
// here where all the results at one level are needed before initiating
|
||||
// any lookups at the next level. Probably not a big deal, but worth noting.
|
||||
if (!inner_pending.empty()) {
|
||||
target_->WaitAll(async_handles, count);
|
||||
}
|
||||
|
||||
// For those that failed to find something, convert to lookup in my
|
||||
// secondary cache.
|
||||
for (AsyncLookupHandle* cur : inner_pending) {
|
||||
if (cur->Result() == nullptr) {
|
||||
// Not found, try my secondary
|
||||
StartAsyncLookupOnMySecondary(*cur);
|
||||
if (cur->IsPending()) {
|
||||
assert(cur->pending_cache == secondary_cache_.get());
|
||||
my_pending.push_back(cur);
|
||||
// Mark as "to be handled by this caller"
|
||||
cur->pending_cache = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wait on all lookups on my secondary cache
|
||||
{
|
||||
std::vector<SecondaryCacheResultHandle*> my_secondary_handles;
|
||||
for (AsyncLookupHandle* cur : my_pending) {
|
||||
my_secondary_handles.push_back(cur->pending_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(std::move(my_secondary_handles));
|
||||
}
|
||||
|
||||
// Process results
|
||||
for (AsyncLookupHandle* cur : my_pending) {
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle(
|
||||
cur->pending_handle);
|
||||
cur->pending_handle = nullptr;
|
||||
cur->result_handle = Promote(
|
||||
std::move(secondary_handle), cur->key, cur->helper, cur->priority,
|
||||
cur->stats, cur->found_dummy_entry, cur->kept_in_sec_cache);
|
||||
assert(cur->pending_cache == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::string CacheWithSecondaryAdapter::GetPrintableOptions() const {
|
||||
std::string str = target_->GetPrintableOptions();
|
||||
str.append(" secondary_cache:\n");
|
||||
str.append(secondary_cache_->GetPrintableOptions());
|
||||
return str;
|
||||
}
|
||||
|
||||
const char* CacheWithSecondaryAdapter::Name() const {
|
||||
// To the user, at least for now, configure the underlying cache with
|
||||
// a secondary cache. So we pretend to be that cache
|
||||
return target_->Name();
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewTieredVolatileCache(
|
||||
TieredVolatileCacheOptions& opts) {
|
||||
if (!opts.cache_opts) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (opts.adm_policy >= TieredAdmissionPolicy::kAdmPolicyMax) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> cache;
|
||||
if (opts.cache_type == PrimaryCacheType::kCacheTypeLRU) {
|
||||
LRUCacheOptions cache_opts =
|
||||
*(static_cast_with_check<LRUCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity += opts.comp_cache_opts.capacity;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else if (opts.cache_type == PrimaryCacheType::kCacheTypeHCC) {
|
||||
HyperClockCacheOptions cache_opts =
|
||||
*(static_cast_with_check<HyperClockCacheOptions, ShardedCacheOptions>(
|
||||
opts.cache_opts));
|
||||
cache_opts.capacity += opts.comp_cache_opts.capacity;
|
||||
cache = cache_opts.MakeSharedCache();
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
std::shared_ptr<SecondaryCache> sec_cache;
|
||||
sec_cache = NewCompressedSecondaryCache(opts.comp_cache_opts);
|
||||
|
||||
return std::make_shared<CacheWithSecondaryAdapter>(
|
||||
cache, sec_cache, opts.adm_policy, /*distribute_cache_res=*/true);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
-78
@@ -1,78 +0,0 @@
|
||||
// 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).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CacheWithSecondaryAdapter : public CacheWrapper {
|
||||
public:
|
||||
explicit CacheWithSecondaryAdapter(
|
||||
std::shared_ptr<Cache> target,
|
||||
std::shared_ptr<SecondaryCache> secondary_cache,
|
||||
TieredAdmissionPolicy adm_policy = TieredAdmissionPolicy::kAdmPolicyAuto,
|
||||
bool distribute_cache_res = false);
|
||||
|
||||
~CacheWithSecondaryAdapter() override;
|
||||
|
||||
Status Insert(const Slice& key, ObjectPtr value,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override;
|
||||
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
CreateContext* create_context,
|
||||
Priority priority = Priority::LOW,
|
||||
Statistics* stats = nullptr) override;
|
||||
|
||||
using Cache::Release;
|
||||
bool Release(Handle* handle, bool erase_if_last_ref = false) override;
|
||||
|
||||
ObjectPtr Value(Handle* handle) override;
|
||||
|
||||
void StartAsyncLookup(AsyncLookupHandle& async_handle) override;
|
||||
|
||||
void WaitAll(AsyncLookupHandle* async_handles, size_t count) override;
|
||||
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
const char* Name() const override;
|
||||
|
||||
Cache* TEST_GetCache() { return target_.get(); }
|
||||
|
||||
SecondaryCache* TEST_GetSecondaryCache() { return secondary_cache_.get(); }
|
||||
|
||||
private:
|
||||
bool EvictionHandler(const Slice& key, Handle* handle, bool was_hit);
|
||||
|
||||
void StartAsyncLookupOnMySecondary(AsyncLookupHandle& async_handle);
|
||||
|
||||
Handle* Promote(
|
||||
std::unique_ptr<SecondaryCacheResultHandle>&& secondary_handle,
|
||||
const Slice& key, const CacheItemHelper* helper, Priority priority,
|
||||
Statistics* stats, bool found_dummy_entry, bool kept_in_sec_cache);
|
||||
|
||||
bool ProcessDummyResult(Cache::Handle** handle, bool erase);
|
||||
|
||||
void CleanupCacheObject(ObjectPtr obj, const CacheItemHelper* helper);
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
TieredAdmissionPolicy adm_policy_;
|
||||
// Whether to proportionally distribute cache memory reservations, i.e
|
||||
// placeholder entries with null value and a non-zero charge, across
|
||||
// the primary and secondary caches.
|
||||
bool distribute_cache_res_;
|
||||
// A cache reservation manager to keep track of secondary cache memory
|
||||
// usage by reserving equivalent capacity against the primary cache
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> pri_cache_res_;
|
||||
// Fraction of a cache memory reservation to be assigned to the secondary
|
||||
// cache
|
||||
double sec_cache_res_ratio_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+7
-44
@@ -13,57 +13,20 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "env/unique_id_gen.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
// The generated seeds must fit in 31 bits so that
|
||||
// ShardedCacheOptions::hash_seed can be set to it explicitly, for
|
||||
// diagnostic/debugging purposes.
|
||||
constexpr uint32_t kSeedMask = 0x7fffffff;
|
||||
uint32_t DetermineSeed(int32_t hash_seed_option) {
|
||||
if (hash_seed_option >= 0) {
|
||||
// User-specified exact seed
|
||||
return static_cast<uint32_t>(hash_seed_option);
|
||||
}
|
||||
static SemiStructuredUniqueIdGen gen;
|
||||
if (hash_seed_option == ShardedCacheOptions::kHostHashSeed) {
|
||||
std::string hostname;
|
||||
Status s = Env::Default()->GetHostNameString(&hostname);
|
||||
if (s.ok()) {
|
||||
return GetSliceHash(hostname) & kSeedMask;
|
||||
} else {
|
||||
// Fall back on something stable within the process.
|
||||
return BitwiseAnd(gen.GetBaseUpper(), kSeedMask);
|
||||
}
|
||||
} else {
|
||||
// for kQuasiRandomHashSeed and fallback
|
||||
uint32_t val = gen.GenerateNext<uint32_t>() & kSeedMask;
|
||||
// Perform some 31-bit bijective transformations so that we get
|
||||
// quasirandom, not just incrementing. (An incrementing seed from a
|
||||
// random starting point would be fine, but hard to describe in a name.)
|
||||
// See https://en.wikipedia.org/wiki/Quasirandom and using a murmur-like
|
||||
// transformation here for our bijection in the lower 31 bits.
|
||||
// See https://en.wikipedia.org/wiki/MurmurHash
|
||||
val *= /*31-bit prime*/ 1150630961;
|
||||
val ^= (val & kSeedMask) >> 17;
|
||||
val *= /*31-bit prime*/ 1320603883;
|
||||
return val & kSeedMask;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ShardedCacheBase::ShardedCacheBase(const ShardedCacheOptions& opts)
|
||||
: Cache(opts.memory_allocator),
|
||||
ShardedCacheBase::ShardedCacheBase(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> allocator)
|
||||
: Cache(std::move(allocator)),
|
||||
last_id_(1),
|
||||
shard_mask_((uint32_t{1} << opts.num_shard_bits) - 1),
|
||||
hash_seed_(DetermineSeed(opts.hash_seed)),
|
||||
strict_capacity_limit_(opts.strict_capacity_limit),
|
||||
capacity_(opts.capacity) {}
|
||||
shard_mask_((uint32_t{1} << num_shard_bits) - 1),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
capacity_(capacity) {}
|
||||
|
||||
size_t ShardedCacheBase::ComputePerShardCapacity(size_t capacity) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
|
||||
Vendored
+55
-50
@@ -15,7 +15,7 @@
|
||||
|
||||
#include "port/lang.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
@@ -34,8 +34,8 @@ class CacheShardBase {
|
||||
std::string GetPrintableOptions() const { return ""; }
|
||||
using HashVal = uint64_t;
|
||||
using HashCref = uint64_t;
|
||||
static inline HashVal ComputeHash(const Slice& key, uint32_t seed) {
|
||||
return GetSliceNPHash64(key, seed);
|
||||
static inline HashVal ComputeHash(const Slice& key) {
|
||||
return GetSliceNPHash64(key);
|
||||
}
|
||||
static inline uint32_t HashPieceForSharding(HashCref hash) {
|
||||
return Lower32of64(hash);
|
||||
@@ -49,19 +49,21 @@ class CacheShardBase {
|
||||
HashCref GetHash() const;
|
||||
...
|
||||
};
|
||||
Status Insert(const Slice& key, HashCref hash, Cache::ObjectPtr value,
|
||||
Status Insert(const Slice& key, HashCref hash, void* value, size_t charge,
|
||||
DeleterFn deleter, HandleImpl** handle,
|
||||
Cache::Priority priority) = 0;
|
||||
Status Insert(const Slice& key, HashCref hash, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
HandleImpl** handle, Cache::Priority priority,
|
||||
bool standalone) = 0;
|
||||
Handle* CreateStandalone(const Slice& key, HashCref hash, ObjectPtr obj,
|
||||
const CacheItemHelper* helper,
|
||||
size_t charge, bool allow_uncharged) = 0;
|
||||
HandleImpl** handle, Cache::Priority priority) = 0;
|
||||
HandleImpl* Lookup(const Slice& key, HashCref hash) = 0;
|
||||
HandleImpl* Lookup(const Slice& key, HashCref hash,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::CreateContext* create_context,
|
||||
Cache::Priority priority,
|
||||
const Cache::CreateCallback& create_cb,
|
||||
Cache::Priority priority, bool wait,
|
||||
Statistics* stats) = 0;
|
||||
bool Release(HandleImpl* handle, bool useful, bool erase_if_last_ref) = 0;
|
||||
bool IsReady(HandleImpl* handle) = 0;
|
||||
void Wait(HandleImpl* handle) = 0;
|
||||
bool Ref(HandleImpl* handle) = 0;
|
||||
void Erase(const Slice& key, HashCref hash) = 0;
|
||||
void SetCapacity(size_t capacity) = 0;
|
||||
@@ -75,9 +77,8 @@ class CacheShardBase {
|
||||
// *state == 0 and implementation sets *state = SIZE_MAX to indicate
|
||||
// completion.
|
||||
void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, ObjectPtr value,
|
||||
size_t charge,
|
||||
const Cache::CacheItemHelper* helper)>& callback,
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
size_t average_entries_per_lock, size_t* state) = 0;
|
||||
void EraseUnRefEntries() = 0;
|
||||
*/
|
||||
@@ -89,7 +90,9 @@ class CacheShardBase {
|
||||
// Portions of ShardedCache that do not depend on the template parameter
|
||||
class ShardedCacheBase : public Cache {
|
||||
public:
|
||||
explicit ShardedCacheBase(const ShardedCacheOptions& opts);
|
||||
ShardedCacheBase(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator);
|
||||
virtual ~ShardedCacheBase() = default;
|
||||
|
||||
int GetNumShardBits() const;
|
||||
@@ -104,8 +107,6 @@ class ShardedCacheBase : public Cache {
|
||||
size_t GetUsage(Handle* handle) const override;
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
uint32_t GetHashSeed() const override { return hash_seed_; }
|
||||
|
||||
protected: // fns
|
||||
virtual void AppendPrintableOptions(std::string& str) const = 0;
|
||||
size_t GetPerShardCapacity() const;
|
||||
@@ -114,7 +115,6 @@ class ShardedCacheBase : public Cache {
|
||||
protected: // data
|
||||
std::atomic<uint64_t> last_id_; // For NewId
|
||||
const uint32_t shard_mask_;
|
||||
const uint32_t hash_seed_;
|
||||
|
||||
// Dynamic configuration parameters, guarded by config_mutex_
|
||||
bool strict_capacity_limit_;
|
||||
@@ -135,8 +135,10 @@ class ShardedCache : public ShardedCacheBase {
|
||||
using HashCref = typename CacheShard::HashCref;
|
||||
using HandleImpl = typename CacheShard::HandleImpl;
|
||||
|
||||
explicit ShardedCache(const ShardedCacheOptions& opts)
|
||||
: ShardedCacheBase(opts),
|
||||
ShardedCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> allocator)
|
||||
: ShardedCacheBase(capacity, num_shard_bits, strict_capacity_limit,
|
||||
allocator),
|
||||
shards_(reinterpret_cast<CacheShard*>(port::cacheline_aligned_alloc(
|
||||
sizeof(CacheShard) * GetNumShards()))),
|
||||
destroy_shards_in_dtor_(false) {}
|
||||
@@ -170,38 +172,41 @@ class ShardedCache : public ShardedCacheBase {
|
||||
[s_c_l](CacheShard* cs) { cs->SetStrictCapacityLimit(s_c_l); });
|
||||
}
|
||||
|
||||
Status Insert(const Slice& key, ObjectPtr obj, const CacheItemHelper* helper,
|
||||
Status Insert(const Slice& key, void* value, size_t charge, DeleterFn deleter,
|
||||
Handle** handle, Priority priority) override {
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
auto h_out = reinterpret_cast<HandleImpl**>(handle);
|
||||
return GetShard(hash).Insert(key, hash, value, charge, deleter, h_out,
|
||||
priority);
|
||||
}
|
||||
Status Insert(const Slice& key, void* value, const CacheItemHelper* helper,
|
||||
size_t charge, Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override {
|
||||
assert(helper);
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
if (!helper) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
auto h_out = reinterpret_cast<HandleImpl**>(handle);
|
||||
return GetShard(hash).Insert(key, hash, obj, helper, charge, h_out,
|
||||
return GetShard(hash).Insert(key, hash, value, helper, charge, h_out,
|
||||
priority);
|
||||
}
|
||||
|
||||
Handle* CreateStandalone(const Slice& key, ObjectPtr obj,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
bool allow_uncharged) override {
|
||||
assert(helper);
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
HandleImpl* result = GetShard(hash).CreateStandalone(
|
||||
key, hash, obj, helper, charge, allow_uncharged);
|
||||
Handle* Lookup(const Slice& key, Statistics* /*stats*/) override {
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
HandleImpl* result = GetShard(hash).Lookup(key, hash);
|
||||
return reinterpret_cast<Handle*>(result);
|
||||
}
|
||||
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper = nullptr,
|
||||
CreateContext* create_context = nullptr,
|
||||
Priority priority = Priority::LOW,
|
||||
Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
const CreateCallback& create_cb, Priority priority, bool wait,
|
||||
Statistics* stats = nullptr) override {
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
HandleImpl* result = GetShard(hash).Lookup(key, hash, helper,
|
||||
create_context, priority, stats);
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
HandleImpl* result = GetShard(hash).Lookup(key, hash, helper, create_cb,
|
||||
priority, wait, stats);
|
||||
return reinterpret_cast<Handle*>(result);
|
||||
}
|
||||
|
||||
void Erase(const Slice& key) override {
|
||||
HashVal hash = CacheShard::ComputeHash(key, hash_seed_);
|
||||
HashVal hash = CacheShard::ComputeHash(key);
|
||||
GetShard(hash).Erase(key, hash);
|
||||
}
|
||||
|
||||
@@ -210,6 +215,14 @@ class ShardedCache : public ShardedCacheBase {
|
||||
auto h = reinterpret_cast<HandleImpl*>(handle);
|
||||
return GetShard(h->GetHash()).Release(h, useful, erase_if_last_ref);
|
||||
}
|
||||
bool IsReady(Handle* handle) override {
|
||||
auto h = reinterpret_cast<HandleImpl*>(handle);
|
||||
return GetShard(h->GetHash()).IsReady(h);
|
||||
}
|
||||
void Wait(Handle* handle) override {
|
||||
auto h = reinterpret_cast<HandleImpl*>(handle);
|
||||
GetShard(h->GetHash()).Wait(h);
|
||||
}
|
||||
bool Ref(Handle* handle) override {
|
||||
auto h = reinterpret_cast<HandleImpl*>(handle);
|
||||
return GetShard(h->GetHash()).Ref(h);
|
||||
@@ -225,14 +238,14 @@ class ShardedCache : public ShardedCacheBase {
|
||||
return SumOverShards2(&CacheShard::GetPinnedUsage);
|
||||
}
|
||||
size_t GetOccupancyCount() const override {
|
||||
return SumOverShards2(&CacheShard::GetOccupancyCount);
|
||||
return SumOverShards2(&CacheShard::GetPinnedUsage);
|
||||
}
|
||||
size_t GetTableAddressCount() const override {
|
||||
return SumOverShards2(&CacheShard::GetTableAddressCount);
|
||||
}
|
||||
void ApplyToAllEntries(
|
||||
const std::function<void(const Slice& key, ObjectPtr value, size_t charge,
|
||||
const CacheItemHelper* helper)>& callback,
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
const ApplyToAllEntriesOptions& opts) override {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
// Iterate over part of each shard, rotating between shards, to
|
||||
@@ -273,14 +286,6 @@ class ShardedCache : public ShardedCacheBase {
|
||||
}
|
||||
}
|
||||
|
||||
inline void ForEachShard(
|
||||
const std::function<void(const CacheShard*)>& fn) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
for (uint32_t i = 0; i < num_shards; i++) {
|
||||
fn(shards_ + i);
|
||||
}
|
||||
}
|
||||
|
||||
inline size_t SumOverShards(
|
||||
const std::function<size_t(CacheShard&)>& fn) const {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
|
||||
Vendored
-375
@@ -1,375 +0,0 @@
|
||||
// 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).
|
||||
|
||||
// APIs for accessing Cache in a type-safe and convenient way. Cache is kept
|
||||
// at a low, thin level of abstraction so that different implementations can
|
||||
// be plugged in, but these wrappers provide clean, convenient access to the
|
||||
// most common operations.
|
||||
//
|
||||
// A number of template classes are needed for sharing common structure. The
|
||||
// key classes are these:
|
||||
//
|
||||
// * PlaceholderCacheInterface - Used for making cache reservations, with
|
||||
// entries that have a charge but no value.
|
||||
// * BasicTypedCacheInterface<TValue> - Used for primary cache storage of
|
||||
// objects of type TValue.
|
||||
// * FullTypedCacheHelper<TValue, TCreateContext> - Used for secondary cache
|
||||
// compatible storage of objects of type TValue.
|
||||
// * For each of these, there's a "Shared" version
|
||||
// (e.g. FullTypedSharedCacheInterface) that holds a shared_ptr to the Cache,
|
||||
// rather than assuming external ownership by holding only a raw `Cache*`.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "rocksdb/advanced_options.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// For future consideration:
|
||||
// * Pass in value to Insert with std::unique_ptr& to simplify ownership
|
||||
// transfer logic in callers
|
||||
// * Make key type a template parameter (e.g. useful for table cache)
|
||||
// * Closer integration with CacheHandleGuard (opt-in, so not always
|
||||
// paying the extra overhead)
|
||||
|
||||
#define CACHE_TYPE_DEFS() \
|
||||
using Priority = Cache::Priority; \
|
||||
using Handle = Cache::Handle; \
|
||||
using ObjectPtr = Cache::ObjectPtr; \
|
||||
using CreateContext = Cache::CreateContext; \
|
||||
using CacheItemHelper = Cache::CacheItemHelper /* caller ; */
|
||||
|
||||
template <typename CachePtr>
|
||||
class BaseCacheInterface {
|
||||
public:
|
||||
CACHE_TYPE_DEFS();
|
||||
|
||||
/*implicit*/ BaseCacheInterface(CachePtr cache) : cache_(std::move(cache)) {}
|
||||
|
||||
inline void Release(Handle* handle) { cache_->Release(handle); }
|
||||
|
||||
inline void ReleaseAndEraseIfLastRef(Handle* handle) {
|
||||
cache_->Release(handle, /*erase_if_last_ref*/ true);
|
||||
}
|
||||
|
||||
inline void RegisterReleaseAsCleanup(Handle* handle, Cleanable& cleanable) {
|
||||
cleanable.RegisterCleanup(&ReleaseCacheHandleCleanup, get(), handle);
|
||||
}
|
||||
|
||||
inline Cache* get() const { return &*cache_; }
|
||||
|
||||
explicit inline operator bool() const noexcept { return cache_ != nullptr; }
|
||||
|
||||
protected:
|
||||
CachePtr cache_;
|
||||
};
|
||||
|
||||
// PlaceholderCacheInterface - Used for making cache reservations, with
|
||||
// entries that have a charge but no value. CacheEntryRole is required as
|
||||
// a template parameter.
|
||||
template <CacheEntryRole kRole, typename CachePtr = Cache*>
|
||||
class PlaceholderCacheInterface : public BaseCacheInterface<CachePtr> {
|
||||
public:
|
||||
CACHE_TYPE_DEFS();
|
||||
using BaseCacheInterface<CachePtr>::BaseCacheInterface;
|
||||
|
||||
inline Status Insert(const Slice& key, size_t charge, Handle** handle) {
|
||||
return this->cache_->Insert(key, /*value=*/nullptr, GetHelper(), charge,
|
||||
handle);
|
||||
}
|
||||
|
||||
static const Cache::CacheItemHelper* GetHelper() {
|
||||
static const Cache::CacheItemHelper kHelper{kRole};
|
||||
return &kHelper;
|
||||
}
|
||||
};
|
||||
|
||||
template <CacheEntryRole kRole>
|
||||
using PlaceholderSharedCacheInterface =
|
||||
PlaceholderCacheInterface<kRole, std::shared_ptr<Cache>>;
|
||||
|
||||
template <class TValue>
|
||||
class BasicTypedCacheHelperFns {
|
||||
public:
|
||||
CACHE_TYPE_DEFS();
|
||||
// E.g. char* for char[]
|
||||
using TValuePtr = std::remove_extent_t<TValue>*;
|
||||
|
||||
protected:
|
||||
inline static ObjectPtr UpCastValue(TValuePtr value) { return value; }
|
||||
inline static TValuePtr DownCastValue(ObjectPtr value) {
|
||||
return static_cast<TValuePtr>(value);
|
||||
}
|
||||
|
||||
static void Delete(ObjectPtr value, MemoryAllocator* allocator) {
|
||||
// FIXME: Currently, no callers actually allocate the ObjectPtr objects
|
||||
// using the custom allocator, just subobjects that keep a reference to
|
||||
// the allocator themselves (with CacheAllocationPtr).
|
||||
if (/*DISABLED*/ false && allocator) {
|
||||
if constexpr (std::is_destructible_v<TValue>) {
|
||||
DownCastValue(value)->~TValue();
|
||||
}
|
||||
allocator->Deallocate(value);
|
||||
} else {
|
||||
// Like delete but properly handles TValue=char[] etc.
|
||||
std::default_delete<TValue>{}(DownCastValue(value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// In its own class to try to minimize the number of distinct CacheItemHelper
|
||||
// instances (e.g. don't vary by CachePtr)
|
||||
template <class TValue, CacheEntryRole kRole>
|
||||
class BasicTypedCacheHelper : public BasicTypedCacheHelperFns<TValue> {
|
||||
public:
|
||||
static const Cache::CacheItemHelper* GetBasicHelper() {
|
||||
static const Cache::CacheItemHelper kHelper{kRole,
|
||||
&BasicTypedCacheHelper::Delete};
|
||||
return &kHelper;
|
||||
}
|
||||
};
|
||||
|
||||
// BasicTypedCacheInterface - Used for primary cache storage of objects of
|
||||
// type TValue, which can be cleaned up with std::default_delete<TValue>. The
|
||||
// role is provided by TValue::kCacheEntryRole or given in an optional
|
||||
// template parameter.
|
||||
template <class TValue, CacheEntryRole kRole = TValue::kCacheEntryRole,
|
||||
typename CachePtr = Cache*>
|
||||
class BasicTypedCacheInterface : public BaseCacheInterface<CachePtr>,
|
||||
public BasicTypedCacheHelper<TValue, kRole> {
|
||||
public:
|
||||
CACHE_TYPE_DEFS();
|
||||
using typename BasicTypedCacheHelperFns<TValue>::TValuePtr;
|
||||
struct TypedHandle : public Handle {};
|
||||
using BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper;
|
||||
// ctor
|
||||
using BaseCacheInterface<CachePtr>::BaseCacheInterface;
|
||||
struct TypedAsyncLookupHandle : public Cache::AsyncLookupHandle {
|
||||
TypedHandle* Result() {
|
||||
return reinterpret_cast<TypedHandle*>(Cache::AsyncLookupHandle::Result());
|
||||
}
|
||||
};
|
||||
|
||||
inline Status Insert(const Slice& key, TValuePtr value, size_t charge,
|
||||
TypedHandle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) {
|
||||
auto untyped_handle = reinterpret_cast<Handle**>(handle);
|
||||
return this->cache_->Insert(
|
||||
key, BasicTypedCacheHelperFns<TValue>::UpCastValue(value),
|
||||
GetBasicHelper(), charge, untyped_handle, priority);
|
||||
}
|
||||
|
||||
inline TypedHandle* Lookup(const Slice& key, Statistics* stats = nullptr) {
|
||||
return reinterpret_cast<TypedHandle*>(
|
||||
this->cache_->BasicLookup(key, stats));
|
||||
}
|
||||
|
||||
inline void StartAsyncLookup(TypedAsyncLookupHandle& async_handle) {
|
||||
assert(async_handle.helper == nullptr);
|
||||
this->cache_->StartAsyncLookup(async_handle);
|
||||
}
|
||||
|
||||
inline CacheHandleGuard<TValue> Guard(TypedHandle* handle) {
|
||||
if (handle) {
|
||||
return CacheHandleGuard<TValue>(&*this->cache_, handle);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
inline std::shared_ptr<TValue> SharedGuard(TypedHandle* handle) {
|
||||
if (handle) {
|
||||
return MakeSharedCacheHandleGuard<TValue>(&*this->cache_, handle);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
inline TValuePtr Value(TypedHandle* handle) {
|
||||
return BasicTypedCacheHelperFns<TValue>::DownCastValue(
|
||||
this->cache_->Value(handle));
|
||||
}
|
||||
};
|
||||
|
||||
// BasicTypedSharedCacheInterface - Like BasicTypedCacheInterface but with a
|
||||
// shared_ptr<Cache> for keeping Cache alive.
|
||||
template <class TValue, CacheEntryRole kRole = TValue::kCacheEntryRole>
|
||||
using BasicTypedSharedCacheInterface =
|
||||
BasicTypedCacheInterface<TValue, kRole, std::shared_ptr<Cache>>;
|
||||
|
||||
// TValue must implement ContentSlice() and ~TValue
|
||||
// TCreateContext must implement Create(std::unique_ptr<TValue>*, ...)
|
||||
template <class TValue, class TCreateContext>
|
||||
class FullTypedCacheHelperFns : public BasicTypedCacheHelperFns<TValue> {
|
||||
public:
|
||||
CACHE_TYPE_DEFS();
|
||||
|
||||
protected:
|
||||
using typename BasicTypedCacheHelperFns<TValue>::TValuePtr;
|
||||
using BasicTypedCacheHelperFns<TValue>::DownCastValue;
|
||||
using BasicTypedCacheHelperFns<TValue>::UpCastValue;
|
||||
|
||||
static size_t Size(ObjectPtr v) {
|
||||
TValuePtr value = DownCastValue(v);
|
||||
auto slice = value->ContentSlice();
|
||||
return slice.size();
|
||||
}
|
||||
|
||||
static Status SaveTo(ObjectPtr v, size_t from_offset, size_t length,
|
||||
char* out) {
|
||||
TValuePtr value = DownCastValue(v);
|
||||
auto slice = value->ContentSlice();
|
||||
assert(from_offset < slice.size());
|
||||
assert(from_offset + length <= slice.size());
|
||||
std::copy_n(slice.data() + from_offset, length, out);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
static Status Create(const Slice& data, CreateContext* context,
|
||||
MemoryAllocator* allocator, ObjectPtr* out_obj,
|
||||
size_t* out_charge) {
|
||||
std::unique_ptr<TValue> value = nullptr;
|
||||
if constexpr (sizeof(TCreateContext) > 0) {
|
||||
TCreateContext* tcontext = static_cast<TCreateContext*>(context);
|
||||
tcontext->Create(&value, out_charge, data, allocator);
|
||||
} else {
|
||||
TCreateContext::Create(&value, out_charge, data, allocator);
|
||||
}
|
||||
*out_obj = UpCastValue(value.release());
|
||||
return Status::OK();
|
||||
}
|
||||
};
|
||||
|
||||
// In its own class to try to minimize the number of distinct CacheItemHelper
|
||||
// instances (e.g. don't vary by CachePtr)
|
||||
template <class TValue, class TCreateContext, CacheEntryRole kRole>
|
||||
class FullTypedCacheHelper
|
||||
: public FullTypedCacheHelperFns<TValue, TCreateContext> {
|
||||
public:
|
||||
static const Cache::CacheItemHelper* GetFullHelper() {
|
||||
static const Cache::CacheItemHelper kHelper{
|
||||
kRole,
|
||||
&FullTypedCacheHelper::Delete,
|
||||
&FullTypedCacheHelper::Size,
|
||||
&FullTypedCacheHelper::SaveTo,
|
||||
&FullTypedCacheHelper::Create,
|
||||
BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper()};
|
||||
return &kHelper;
|
||||
}
|
||||
};
|
||||
|
||||
// FullTypedCacheHelper - Used for secondary cache compatible storage of
|
||||
// objects of type TValue. In addition to BasicTypedCacheInterface constraints,
|
||||
// we require TValue::ContentSlice() to return persistable data. This
|
||||
// simplifies usage for the normal case of simple secondary cache compatibility
|
||||
// (can give you a Slice to the data already in memory). In addition to
|
||||
// TCreateContext performing the role of Cache::CreateContext, it is also
|
||||
// expected to provide a function Create(std::unique_ptr<TValue>* value,
|
||||
// size_t* out_charge, const Slice& data, MemoryAllocator* allocator) for
|
||||
// creating new TValue.
|
||||
template <class TValue, class TCreateContext,
|
||||
CacheEntryRole kRole = TValue::kCacheEntryRole,
|
||||
typename CachePtr = Cache*>
|
||||
class FullTypedCacheInterface
|
||||
: public BasicTypedCacheInterface<TValue, kRole, CachePtr>,
|
||||
public FullTypedCacheHelper<TValue, TCreateContext, kRole> {
|
||||
public:
|
||||
CACHE_TYPE_DEFS();
|
||||
using typename BasicTypedCacheInterface<TValue, kRole, CachePtr>::TypedHandle;
|
||||
using typename BasicTypedCacheInterface<TValue, kRole,
|
||||
CachePtr>::TypedAsyncLookupHandle;
|
||||
using typename BasicTypedCacheHelperFns<TValue>::TValuePtr;
|
||||
using BasicTypedCacheHelper<TValue, kRole>::GetBasicHelper;
|
||||
using FullTypedCacheHelper<TValue, TCreateContext, kRole>::GetFullHelper;
|
||||
using BasicTypedCacheHelperFns<TValue>::UpCastValue;
|
||||
using BasicTypedCacheHelperFns<TValue>::DownCastValue;
|
||||
// ctor
|
||||
using BasicTypedCacheInterface<TValue, kRole,
|
||||
CachePtr>::BasicTypedCacheInterface;
|
||||
|
||||
// Insert with SecondaryCache compatibility (subject to CacheTier).
|
||||
// (Basic Insert() also inherited.)
|
||||
inline Status InsertFull(
|
||||
const Slice& key, TValuePtr value, size_t charge,
|
||||
TypedHandle** handle = nullptr, Priority priority = Priority::LOW,
|
||||
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||||
auto untyped_handle = reinterpret_cast<Handle**>(handle);
|
||||
auto helper = lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier
|
||||
? GetFullHelper()
|
||||
: GetBasicHelper();
|
||||
return this->cache_->Insert(key, UpCastValue(value), helper, charge,
|
||||
untyped_handle, priority);
|
||||
}
|
||||
|
||||
// Like SecondaryCache::InsertSaved, with SecondaryCache compatibility
|
||||
// (subject to CacheTier).
|
||||
inline Status InsertSaved(
|
||||
const Slice& key, const Slice& data, TCreateContext* create_context,
|
||||
Priority priority = Priority::LOW,
|
||||
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier,
|
||||
size_t* out_charge = nullptr) {
|
||||
ObjectPtr value;
|
||||
size_t charge;
|
||||
Status st = GetFullHelper()->create_cb(data, create_context,
|
||||
this->cache_->memory_allocator(),
|
||||
&value, &charge);
|
||||
if (out_charge) {
|
||||
*out_charge = charge;
|
||||
}
|
||||
if (st.ok()) {
|
||||
st = InsertFull(key, DownCastValue(value), charge, nullptr /*handle*/,
|
||||
priority, lowest_used_cache_tier);
|
||||
} else {
|
||||
GetFullHelper()->del_cb(value, this->cache_->memory_allocator());
|
||||
}
|
||||
return st;
|
||||
}
|
||||
|
||||
// Lookup with SecondaryCache support (subject to CacheTier).
|
||||
// (Basic Lookup() also inherited.)
|
||||
inline TypedHandle* LookupFull(
|
||||
const Slice& key, TCreateContext* create_context = nullptr,
|
||||
Priority priority = Priority::LOW, Statistics* stats = nullptr,
|
||||
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||||
if (lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier) {
|
||||
return reinterpret_cast<TypedHandle*>(this->cache_->Lookup(
|
||||
key, GetFullHelper(), create_context, priority, stats));
|
||||
} else {
|
||||
return BasicTypedCacheInterface<TValue, kRole, CachePtr>::Lookup(key,
|
||||
stats);
|
||||
}
|
||||
}
|
||||
|
||||
inline void StartAsyncLookupFull(
|
||||
TypedAsyncLookupHandle& async_handle,
|
||||
CacheTier lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier) {
|
||||
if (lowest_used_cache_tier == CacheTier::kNonVolatileBlockTier) {
|
||||
async_handle.helper = GetFullHelper();
|
||||
this->cache_->StartAsyncLookup(async_handle);
|
||||
} else {
|
||||
BasicTypedCacheInterface<TValue, kRole, CachePtr>::StartAsyncLookup(
|
||||
async_handle);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// FullTypedSharedCacheInterface - Like FullTypedCacheInterface but with a
|
||||
// shared_ptr<Cache> for keeping Cache alive.
|
||||
template <class TValue, class TCreateContext,
|
||||
CacheEntryRole kRole = TValue::kCacheEntryRole>
|
||||
using FullTypedSharedCacheInterface =
|
||||
FullTypedCacheInterface<TValue, TCreateContext, kRole,
|
||||
std::shared_ptr<Cache>>;
|
||||
|
||||
#undef CACHE_TYPE_DEFS
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -12,7 +12,7 @@ fi
|
||||
ROOT=".."
|
||||
# Fetch right version of gcov
|
||||
if [ -d /mnt/gvfs/third-party -a -z "$CXX" ]; then
|
||||
source $ROOT/build_tools/fbcode_config_platform010.sh
|
||||
source $ROOT/build_tools/fbcode_config_platform009.sh
|
||||
GCOV=$GCC_BASE/bin/gcov
|
||||
else
|
||||
GCOV=$(which gcov)
|
||||
|
||||
+1
-15
@@ -8,7 +8,7 @@ DB_STRESS_CMD?=./db_stress
|
||||
include common.mk
|
||||
|
||||
CRASHTEST_MAKE=$(MAKE) -f crash_test.mk
|
||||
CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --cleanup_cmd='$(DB_CLEANUP_CMD)'
|
||||
CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD)
|
||||
|
||||
.PHONY: crash_test crash_test_with_atomic_flush crash_test_with_txn \
|
||||
crash_test_with_best_efforts_recovery crash_test_with_ts \
|
||||
@@ -21,8 +21,6 @@ CRASHTEST_PY=$(PYTHON) -u tools/db_crashtest.py --stress_cmd=$(DB_STRESS_CMD) --
|
||||
blackbox_crash_test_with_multiops_wp_txn \
|
||||
crash_test_with_tiered_storage blackbox_crash_test_with_tiered_storage \
|
||||
whitebox_crash_test_with_tiered_storage \
|
||||
whitebox_crash_test_with_optimistic_txn \
|
||||
blackbox_crash_test_with_optimistic_txn \
|
||||
|
||||
crash_test: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
@@ -39,11 +37,6 @@ crash_test_with_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_txn
|
||||
|
||||
crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_optimistic_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_optimistic_txn
|
||||
|
||||
crash_test_with_best_efforts_recovery: blackbox_crash_test_with_best_efforts_recovery
|
||||
|
||||
crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
@@ -87,9 +80,6 @@ blackbox_crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
|
||||
blackbox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_tiered_storage blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --optimistic_txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
@@ -115,7 +105,3 @@ whitebox_crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
whitebox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_tiered_storage whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_optimistic_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --optimistic_txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
@@ -8,7 +8,6 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
|
||||
#include "memory/arena.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -47,11 +46,6 @@ void ArenaWrappedDBIter::Init(
|
||||
read_options_ = read_options;
|
||||
allow_refresh_ = allow_refresh;
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
|
||||
if (!CheckFSFeatureSupport(env->GetFileSystem().get(),
|
||||
FSSupportedOps::kAsyncIO)) {
|
||||
read_options_.async_io = false;
|
||||
}
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh() {
|
||||
|
||||
@@ -9,9 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
std::unique_ptr<BlobContents> BlobContents::Create(
|
||||
CacheAllocationPtr&& allocation, size_t size) {
|
||||
return std::unique_ptr<BlobContents>(
|
||||
new BlobContents(std::move(allocation), size));
|
||||
}
|
||||
|
||||
size_t BlobContents::ApproximateMemoryUsage() const {
|
||||
size_t usage = 0;
|
||||
|
||||
@@ -39,4 +45,46 @@ size_t BlobContents::ApproximateMemoryUsage() const {
|
||||
return usage;
|
||||
}
|
||||
|
||||
size_t BlobContents::SizeCallback(void* obj) {
|
||||
assert(obj);
|
||||
|
||||
return static_cast<const BlobContents*>(obj)->size();
|
||||
}
|
||||
|
||||
Status BlobContents::SaveToCallback(void* from_obj, size_t from_offset,
|
||||
size_t length, void* out) {
|
||||
assert(from_obj);
|
||||
|
||||
const BlobContents* buf = static_cast<const BlobContents*>(from_obj);
|
||||
assert(buf->size() >= from_offset + length);
|
||||
|
||||
memcpy(out, buf->data().data() + from_offset, length);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Cache::CacheItemHelper* BlobContents::GetCacheItemHelper() {
|
||||
static Cache::CacheItemHelper cache_helper(
|
||||
&SizeCallback, &SaveToCallback,
|
||||
GetCacheEntryDeleterForRole<BlobContents, CacheEntryRole::kBlobValue>());
|
||||
|
||||
return &cache_helper;
|
||||
}
|
||||
|
||||
Status BlobContents::CreateCallback(CacheAllocationPtr&& allocation,
|
||||
const void* buf, size_t size,
|
||||
void** out_obj, size_t* charge) {
|
||||
assert(allocation);
|
||||
|
||||
memcpy(allocation.get(), buf, size);
|
||||
|
||||
std::unique_ptr<BlobContents> obj = Create(std::move(allocation), size);
|
||||
BlobContents* const contents = obj.release();
|
||||
|
||||
*out_obj = contents;
|
||||
*charge = contents->ApproximateMemoryUsage();
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+17
-20
@@ -7,8 +7,8 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "memory/memory_allocator_impl.h"
|
||||
#include "rocksdb/advanced_cache.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -18,8 +18,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// A class representing a single uncompressed value read from a blob file.
|
||||
class BlobContents {
|
||||
public:
|
||||
BlobContents(CacheAllocationPtr&& allocation, size_t size)
|
||||
: allocation_(std::move(allocation)), data_(allocation_.get(), size) {}
|
||||
static std::unique_ptr<BlobContents> Create(CacheAllocationPtr&& allocation,
|
||||
size_t size);
|
||||
|
||||
BlobContents(const BlobContents&) = delete;
|
||||
BlobContents& operator=(const BlobContents&) = delete;
|
||||
@@ -34,26 +34,23 @@ class BlobContents {
|
||||
|
||||
size_t ApproximateMemoryUsage() const;
|
||||
|
||||
// For TypedCacheInterface
|
||||
const Slice& ContentSlice() const { return data_; }
|
||||
static constexpr CacheEntryRole kCacheEntryRole = CacheEntryRole::kBlobValue;
|
||||
// Callbacks for secondary cache
|
||||
static size_t SizeCallback(void* obj);
|
||||
|
||||
static Status SaveToCallback(void* from_obj, size_t from_offset,
|
||||
size_t length, void* out);
|
||||
|
||||
static Cache::CacheItemHelper* GetCacheItemHelper();
|
||||
|
||||
static Status CreateCallback(CacheAllocationPtr&& allocation, const void* buf,
|
||||
size_t size, void** out_obj, size_t* charge);
|
||||
|
||||
private:
|
||||
BlobContents(CacheAllocationPtr&& allocation, size_t size)
|
||||
: allocation_(std::move(allocation)), data_(allocation_.get(), size) {}
|
||||
|
||||
CacheAllocationPtr allocation_;
|
||||
Slice data_;
|
||||
};
|
||||
|
||||
class BlobContentsCreator : public Cache::CreateContext {
|
||||
public:
|
||||
static void Create(std::unique_ptr<BlobContents>* out, size_t* out_charge,
|
||||
const Slice& contents, MemoryAllocator* alloc) {
|
||||
auto raw = new BlobContents(AllocateAndCopyBlock(contents, alloc),
|
||||
contents.size());
|
||||
out->reset(raw);
|
||||
if (out_charge) {
|
||||
*out_charge = raw->ApproximateMemoryUsage();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -123,10 +123,6 @@ class BlobCountingIterator : public InternalIterator {
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
bool IsDeleteRangeSentinelKey() const override {
|
||||
return iter_->IsDeleteRangeSentinelKey();
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateAndCountBlobIfNeeded() {
|
||||
assert(!iter_->Valid() || iter_->status().ok());
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
#include "db/blob/blob_source.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/version_set.h"
|
||||
#include "file/filename.h"
|
||||
@@ -259,9 +258,8 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// TODO: allow user CompressionOptions, including max_compressed_bytes_per_kb
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(blob_compression_type_, opts);
|
||||
CompressionContext context(blob_compression_type_);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
@@ -395,7 +393,7 @@ Status BlobFileBuilder::PutBlobIntoCacheIfNeeded(const Slice& blob,
|
||||
uint64_t blob_offset) const {
|
||||
Status s = Status::OK();
|
||||
|
||||
BlobSource::SharedCacheInterface blob_cache{immutable_options_->blob_cache};
|
||||
auto blob_cache = immutable_options_->blob_cache;
|
||||
auto statistics = immutable_options_->statistics.get();
|
||||
bool warm_cache =
|
||||
prepopulate_blob_cache_ == PrepopulateBlobCache::kFlushOnly &&
|
||||
@@ -409,12 +407,34 @@ Status BlobFileBuilder::PutBlobIntoCacheIfNeeded(const Slice& blob,
|
||||
|
||||
const Cache::Priority priority = Cache::Priority::BOTTOM;
|
||||
|
||||
s = blob_cache.InsertSaved(key, blob, nullptr /*context*/, priority,
|
||||
immutable_options_->lowest_used_cache_tier);
|
||||
// Objects to be put into the cache have to be heap-allocated and
|
||||
// self-contained, i.e. own their contents. The Cache has to be able to
|
||||
// take unique ownership of them.
|
||||
CacheAllocationPtr allocation =
|
||||
AllocateBlock(blob.size(), blob_cache->memory_allocator());
|
||||
memcpy(allocation.get(), blob.data(), blob.size());
|
||||
std::unique_ptr<BlobContents> buf =
|
||||
BlobContents::Create(std::move(allocation), blob.size());
|
||||
|
||||
Cache::CacheItemHelper* const cache_item_helper =
|
||||
BlobContents::GetCacheItemHelper();
|
||||
assert(cache_item_helper);
|
||||
|
||||
if (immutable_options_->lowest_used_cache_tier ==
|
||||
CacheTier::kNonVolatileBlockTier) {
|
||||
s = blob_cache->Insert(key, buf.get(), cache_item_helper,
|
||||
buf->ApproximateMemoryUsage(),
|
||||
nullptr /* cache_handle */, priority);
|
||||
} else {
|
||||
s = blob_cache->Insert(key, buf.get(), buf->ApproximateMemoryUsage(),
|
||||
cache_item_helper->del_cb,
|
||||
nullptr /* cache_handle */, priority);
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
RecordTick(statistics, BLOB_DB_CACHE_ADD);
|
||||
RecordTick(statistics, BLOB_DB_CACHE_BYTES_WRITE, blob.size());
|
||||
RecordTick(statistics, BLOB_DB_CACHE_BYTES_WRITE, buf->size());
|
||||
buf.release();
|
||||
} else {
|
||||
RecordTick(statistics, BLOB_DB_CACHE_ADD_FAILURES);
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
ASSERT_EQ(blob_file_addition.GetTotalBlobCount(), 1);
|
||||
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(kSnappyCompression, opts);
|
||||
CompressionContext context(kSnappyCompression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
|
||||
+12
-11
@@ -25,7 +25,7 @@ BlobFileCache::BlobFileCache(Cache* cache,
|
||||
HistogramImpl* blob_file_read_hist,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: cache_(cache),
|
||||
mutex_(kNumberOfMutexStripes),
|
||||
mutex_(kNumberOfMutexStripes, kGetSliceNPHash64UnseededFnPtr),
|
||||
immutable_options_(immutable_options),
|
||||
file_options_(file_options),
|
||||
column_family_id_(column_family_id),
|
||||
@@ -37,29 +37,29 @@ BlobFileCache::BlobFileCache(Cache* cache,
|
||||
}
|
||||
|
||||
Status BlobFileCache::GetBlobFileReader(
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
const Slice key = GetSliceForKey(&blob_file_number);
|
||||
const Slice key = GetSlice(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
|
||||
TypedHandle* handle = cache_.Lookup(key);
|
||||
Cache::Handle* handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
*blob_file_reader = cache_.Guard(handle);
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("BlobFileCache::GetBlobFileReader:DoubleCheck");
|
||||
|
||||
// Check again while holding mutex
|
||||
MutexLock lock(&mutex_.Get(key));
|
||||
MutexLock lock(mutex_.get(key));
|
||||
|
||||
handle = cache_.Lookup(key);
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
*blob_file_reader = cache_.Guard(handle);
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
{
|
||||
assert(file_options_);
|
||||
const Status s = BlobFileReader::Create(
|
||||
*immutable_options_, read_options, *file_options_, column_family_id_,
|
||||
*immutable_options_, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, io_tracer_, &reader);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
@@ -84,7 +84,8 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
{
|
||||
constexpr size_t charge = 1;
|
||||
|
||||
const Status s = cache_.Insert(key, reader.get(), charge, &handle);
|
||||
const Status s = cache_->Insert(key, reader.get(), charge,
|
||||
&DeleteCacheEntry<BlobFileReader>, &handle);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
return s;
|
||||
@@ -93,7 +94,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
|
||||
reader.release();
|
||||
|
||||
*blob_file_reader = cache_.Guard(handle);
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#include "cache/typed_cache.h"
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
@@ -19,6 +18,7 @@ struct ImmutableOptions;
|
||||
struct FileOptions;
|
||||
class HistogramImpl;
|
||||
class Status;
|
||||
class BlobFileReader;
|
||||
class Slice;
|
||||
class IOTracer;
|
||||
|
||||
@@ -32,18 +32,14 @@ class BlobFileCache {
|
||||
BlobFileCache(const BlobFileCache&) = delete;
|
||||
BlobFileCache& operator=(const BlobFileCache&) = delete;
|
||||
|
||||
Status GetBlobFileReader(const ReadOptions& read_options,
|
||||
uint64_t blob_file_number,
|
||||
Status GetBlobFileReader(uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
private:
|
||||
using CacheInterface =
|
||||
BasicTypedCacheInterface<BlobFileReader, CacheEntryRole::kMisc>;
|
||||
using TypedHandle = CacheInterface::TypedHandle;
|
||||
CacheInterface cache_;
|
||||
Cache* cache_;
|
||||
// Note: mutex_ below is used to guard against multiple threads racing to open
|
||||
// the same file.
|
||||
Striped<CacheAlignedWrapper<port::Mutex>> mutex_;
|
||||
Striped<port::Mutex, Slice> mutex_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
const FileOptions* file_options_;
|
||||
uint32_t column_family_id_;
|
||||
|
||||
@@ -118,9 +118,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
// First try: reader should be opened and put in cache
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&first));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -128,8 +126,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
// Second try: reader should be served from cache
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&second));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -166,21 +163,19 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
const ReadOptions read_options;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileCache::GetBlobFileReader:DoubleCheck", [&](void* /* arg */) {
|
||||
// Disabling sync points to prevent infinite recursion
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options,
|
||||
blob_file_number, &second));
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(read_options, blob_file_number,
|
||||
&first));
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
@@ -218,10 +213,8 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
|
||||
.IsIOError());
|
||||
blob_file_cache.GetBlobFileReader(blob_file_number, &reader).IsIOError());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
@@ -260,10 +253,8 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
// strict_capacity_limit is set
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(read_options, blob_file_number, &reader)
|
||||
.IsMemoryLimit());
|
||||
ASSERT_TRUE(blob_file_cache.GetBlobFileReader(blob_file_number, &reader)
|
||||
.IsMemoryLimit());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
|
||||
@@ -23,19 +23,32 @@ class BlobFileCompletionCallback {
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const std::string& dbname)
|
||||
: event_logger_(event_logger), listeners_(listeners), dbname_(dbname) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
sst_file_manager_ = sst_file_manager;
|
||||
mutex_ = mutex;
|
||||
error_handler_ = error_handler;
|
||||
#else
|
||||
(void)sst_file_manager;
|
||||
(void)mutex;
|
||||
(void)error_handler;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void OnBlobFileCreationStarted(const std::string& file_name,
|
||||
const std::string& column_family_name,
|
||||
int job_id,
|
||||
BlobFileCreationReason creation_reason) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Notify the listeners.
|
||||
EventHelpers::NotifyBlobFileCreationStarted(listeners_, dbname_,
|
||||
column_family_name, file_name,
|
||||
job_id, creation_reason);
|
||||
#else
|
||||
(void)file_name;
|
||||
(void)column_family_name;
|
||||
(void)job_id;
|
||||
(void)creation_reason;
|
||||
#endif
|
||||
}
|
||||
|
||||
Status OnBlobFileCompleted(const std::string& file_name,
|
||||
@@ -48,6 +61,7 @@ class BlobFileCompletionCallback {
|
||||
uint64_t blob_count, uint64_t blob_bytes) {
|
||||
Status s;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager_);
|
||||
if (sfm) {
|
||||
// Report new blob files to SstFileManagerImpl
|
||||
@@ -60,6 +74,7 @@ class BlobFileCompletionCallback {
|
||||
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Notify the listeners.
|
||||
EventHelpers::LogAndNotifyBlobFileCreationFinished(
|
||||
@@ -74,9 +89,11 @@ class BlobFileCompletionCallback {
|
||||
}
|
||||
|
||||
private:
|
||||
#ifndef ROCKSDB_LITE
|
||||
SstFileManager* sst_file_manager_;
|
||||
InstrumentedMutex* mutex_;
|
||||
ErrorHandler* error_handler_;
|
||||
#endif // ROCKSDB_LITE
|
||||
EventLogger* event_logger_;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_;
|
||||
std::string dbname_;
|
||||
|
||||
+40
-51
@@ -12,7 +12,7 @@
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/file_prefetch_buffer.h"
|
||||
#include "file/filename.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/slice.h"
|
||||
@@ -26,10 +26,9 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFileReader::Create(
|
||||
const ImmutableOptions& immutable_options, const ReadOptions& read_options,
|
||||
const FileOptions& file_options, uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist, uint64_t blob_file_number,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ImmutableOptions& immutable_options, const FileOptions& file_options,
|
||||
uint32_t column_family_id, HistogramImpl* blob_file_read_hist,
|
||||
uint64_t blob_file_number, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(!*blob_file_reader);
|
||||
@@ -53,17 +52,15 @@ Status BlobFileReader::Create(
|
||||
CompressionType compression_type = kNoCompression;
|
||||
|
||||
{
|
||||
const Status s =
|
||||
ReadHeader(file_reader.get(), read_options, column_family_id,
|
||||
statistics, &compression_type);
|
||||
const Status s = ReadHeader(file_reader.get(), column_family_id, statistics,
|
||||
&compression_type);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const Status s =
|
||||
ReadFooter(file_reader.get(), read_options, file_size, statistics);
|
||||
const Status s = ReadFooter(file_reader.get(), file_size, statistics);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -137,7 +134,6 @@ Status BlobFileReader::OpenFile(
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint32_t column_family_id,
|
||||
Statistics* statistics,
|
||||
CompressionType* compression_type) {
|
||||
@@ -154,9 +150,10 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
constexpr uint64_t read_offset = 0;
|
||||
constexpr size_t read_size = BlobLogHeader::kSize;
|
||||
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_options, read_offset, read_size,
|
||||
statistics, &header_slice, &buf, &aligned_buf);
|
||||
// TODO: rate limit reading headers from blob files.
|
||||
const Status s = ReadFromFile(file_reader, read_offset, read_size,
|
||||
statistics, &header_slice, &buf, &aligned_buf,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -190,7 +187,6 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint64_t file_size, Statistics* statistics) {
|
||||
assert(file_size >= BlobLogHeader::kSize + BlobLogFooter::kSize);
|
||||
assert(file_reader);
|
||||
@@ -205,9 +201,10 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
const uint64_t read_offset = file_size - BlobLogFooter::kSize;
|
||||
constexpr size_t read_size = BlobLogFooter::kSize;
|
||||
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_options, read_offset, read_size,
|
||||
statistics, &footer_slice, &buf, &aligned_buf);
|
||||
// TODO: rate limit reading footers from blob files.
|
||||
const Status s = ReadFromFile(file_reader, read_offset, read_size,
|
||||
statistics, &footer_slice, &buf, &aligned_buf,
|
||||
Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -235,10 +232,10 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Statistics* statistics, Slice* slice,
|
||||
Buffer* buf, AlignedBuf* aligned_buf) {
|
||||
Buffer* buf, AlignedBuf* aligned_buf,
|
||||
Env::IOPriority rate_limiter_priority) {
|
||||
assert(slice);
|
||||
assert(buf);
|
||||
assert(aligned_buf);
|
||||
@@ -249,23 +246,17 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
|
||||
Status s;
|
||||
|
||||
IOOptions io_options;
|
||||
s = file_reader->PrepareIOOptions(read_options, io_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (file_reader->use_direct_io()) {
|
||||
constexpr char* scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, scratch,
|
||||
aligned_buf);
|
||||
s = file_reader->Read(IOOptions(), read_offset, read_size, slice, scratch,
|
||||
aligned_buf, rate_limiter_priority);
|
||||
} else {
|
||||
buf->reset(new char[read_size]);
|
||||
constexpr AlignedBuf* aligned_scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(io_options, read_offset, read_size, slice, buf->get(),
|
||||
aligned_scratch);
|
||||
s = file_reader->Read(IOOptions(), read_offset, read_size, slice,
|
||||
buf->get(), aligned_scratch, rate_limiter_priority);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -333,14 +324,10 @@ Status BlobFileReader::GetBlob(
|
||||
Status s;
|
||||
constexpr bool for_compaction = true;
|
||||
|
||||
IOOptions io_options;
|
||||
s = file_reader_->PrepareIOOptions(read_options, io_options);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
prefetched = prefetch_buffer->TryReadFromCache(
|
||||
io_options, file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size), &record_slice, &s, for_compaction);
|
||||
IOOptions(), file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size), &record_slice, &s,
|
||||
read_options.rate_limiter_priority, for_compaction);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -351,10 +338,10 @@ Status BlobFileReader::GetBlob(
|
||||
PERF_COUNTER_ADD(blob_read_count, 1);
|
||||
PERF_COUNTER_ADD(blob_read_byte, record_size);
|
||||
PERF_TIMER_GUARD(blob_read_time);
|
||||
const Status s =
|
||||
ReadFromFile(file_reader_.get(), read_options, record_offset,
|
||||
static_cast<size_t>(record_size), statistics_,
|
||||
&record_slice, &buf, &aligned_buf);
|
||||
const Status s = ReadFromFile(file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size), statistics_,
|
||||
&record_slice, &buf, &aligned_buf,
|
||||
read_options.rate_limiter_priority);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -433,11 +420,11 @@ void BlobFileReader::MultiGetBlob(
|
||||
assert(req->offset >= adjustment);
|
||||
adjustments.push_back(adjustment);
|
||||
|
||||
FSReadRequest read_req;
|
||||
FSReadRequest read_req = {};
|
||||
read_req.offset = req->offset - adjustment;
|
||||
read_req.len = req->len + adjustment;
|
||||
read_reqs.emplace_back(read_req);
|
||||
total_len += read_req.len;
|
||||
read_reqs.emplace_back(std::move(read_req));
|
||||
}
|
||||
|
||||
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, total_len);
|
||||
@@ -462,12 +449,9 @@ void BlobFileReader::MultiGetBlob(
|
||||
TEST_SYNC_POINT("BlobFileReader::MultiGetBlob:ReadFromFile");
|
||||
PERF_COUNTER_ADD(blob_read_count, num_blobs);
|
||||
PERF_COUNTER_ADD(blob_read_byte, total_len);
|
||||
IOOptions opts;
|
||||
s = file_reader_->PrepareIOOptions(read_options, opts);
|
||||
if (s.ok()) {
|
||||
s = file_reader_->MultiRead(opts, read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr);
|
||||
}
|
||||
s = file_reader_->MultiRead(IOOptions(), read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr,
|
||||
read_options.rate_limiter_priority);
|
||||
if (!s.ok()) {
|
||||
for (auto& req : read_reqs) {
|
||||
req.status.PermitUncheckedError();
|
||||
@@ -585,7 +569,12 @@ Status BlobFileReader::UncompressBlobIfNeeded(
|
||||
assert(result);
|
||||
|
||||
if (compression_type == kNoCompression) {
|
||||
BlobContentsCreator::Create(result, nullptr, value_slice, allocator);
|
||||
CacheAllocationPtr allocation =
|
||||
AllocateBlock(value_slice.size(), allocator);
|
||||
memcpy(allocation.get(), value_slice.data(), value_slice.size());
|
||||
|
||||
*result = BlobContents::Create(std::move(allocation), value_slice.size());
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -613,7 +602,7 @@ Status BlobFileReader::UncompressBlobIfNeeded(
|
||||
return Status::Corruption("Unable to uncompress blob");
|
||||
}
|
||||
|
||||
result->reset(new BlobContents(std::move(output), uncompressed_size));
|
||||
*result = BlobContents::Create(std::move(output), uncompressed_size);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ class Statistics;
|
||||
class BlobFileReader {
|
||||
public:
|
||||
static Status Create(const ImmutableOptions& immutable_options,
|
||||
const ReadOptions& read_options,
|
||||
const FileOptions& file_options,
|
||||
uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist,
|
||||
@@ -75,21 +74,19 @@ class BlobFileReader {
|
||||
std::unique_ptr<RandomAccessFileReader>* file_reader);
|
||||
|
||||
static Status ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint32_t column_family_id, Statistics* statistics,
|
||||
CompressionType* compression_type);
|
||||
|
||||
static Status ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options, uint64_t file_size,
|
||||
Statistics* statistics);
|
||||
uint64_t file_size, Statistics* statistics);
|
||||
|
||||
using Buffer = std::unique_ptr<char[]>;
|
||||
|
||||
static Status ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
const ReadOptions& read_options,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Statistics* statistics, Slice* slice, Buffer* buf,
|
||||
AlignedBuf* aligned_buf);
|
||||
AlignedBuf* aligned_buf,
|
||||
Env::IOPriority rate_limiter_priority);
|
||||
|
||||
static Status VerifyBlob(const Slice& record_slice, const Slice& user_key,
|
||||
uint64_t value_size);
|
||||
|
||||
@@ -74,7 +74,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
@@ -172,12 +172,12 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ReadOptions read_options;
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
// Make sure the blob can be retrieved with and without checksum verification
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
@@ -479,11 +479,11 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -513,11 +513,11 @@ TEST_F(BlobFileReaderTest, TTL) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -552,11 +552,11 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -591,11 +591,11 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -629,9 +629,9 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
constexpr uint32_t incorrect_column_family_id = 2;
|
||||
const ReadOptions read_options;
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, read_options,
|
||||
FileOptions(), incorrect_column_family_id,
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
incorrect_column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
.IsCorruption());
|
||||
@@ -664,10 +664,10 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
const ReadOptions read_options;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::VerifyBlob:CheckBlobCRC", [](void* arg) {
|
||||
@@ -728,12 +728,13 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
ReadOptions read_options;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
// Make sure the blob can be retrieved with and without checksum verification
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
@@ -802,10 +803,10 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
const ReadOptions read_options;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", [](void* arg) {
|
||||
@@ -894,10 +895,10 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
const ReadOptions read_options;
|
||||
|
||||
const Status s = BlobFileReader::Create(
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
|
||||
const bool fail_during_create =
|
||||
(sync_point_ != "BlobFileReader::GetBlob:ReadFromFile");
|
||||
@@ -982,10 +983,10 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
const ReadOptions read_options;
|
||||
|
||||
const Status s = BlobFileReader::Create(
|
||||
immutable_options, read_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
|
||||
const bool fail_during_create =
|
||||
sync_point_ != "BlobFileReader::GetBlob:TamperWithResult";
|
||||
|
||||
@@ -22,8 +22,6 @@ constexpr uint32_t kVersion1 = 1;
|
||||
|
||||
using ExpirationRange = std::pair<uint64_t, uint64_t>;
|
||||
|
||||
// clang-format off
|
||||
|
||||
// Format of blob log file header (30 bytes):
|
||||
//
|
||||
// +--------------+---------+---------+-------+-------------+-------------------+
|
||||
@@ -37,9 +35,6 @@ using ExpirationRange = std::pair<uint64_t, uint64_t>;
|
||||
//
|
||||
// Expiration range in the header is a rough range based on
|
||||
// blob_db_options.ttl_range_secs.
|
||||
|
||||
// clang-format on
|
||||
|
||||
struct BlobLogHeader {
|
||||
static constexpr size_t kSize = 30;
|
||||
|
||||
@@ -62,8 +57,6 @@ struct BlobLogHeader {
|
||||
Status DecodeFrom(Slice slice);
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
// Format of blob log file footer (32 bytes):
|
||||
//
|
||||
// +--------------+------------+-------------------+------------+
|
||||
@@ -76,9 +69,6 @@ struct BlobLogHeader {
|
||||
//
|
||||
// Unlike the same field in file header, expiration range in the footer is the
|
||||
// range of smallest and largest expiration of the data in this file.
|
||||
|
||||
// clang-format on
|
||||
|
||||
struct BlobLogFooter {
|
||||
static constexpr size_t kSize = 32;
|
||||
|
||||
@@ -91,8 +81,6 @@ struct BlobLogFooter {
|
||||
Status DecodeFrom(Slice slice);
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
// Blob record format (32 bytes header + key + value):
|
||||
//
|
||||
// +------------+--------------+------------+------------+----------+---------+-----------+
|
||||
@@ -112,9 +100,6 @@ struct BlobLogFooter {
|
||||
//
|
||||
// We could use variable length encoding (Varint64) to save more space, but it
|
||||
// make reader more complicated.
|
||||
|
||||
// clang-format on
|
||||
|
||||
struct BlobLogRecord {
|
||||
// header include fields up to blob CRC
|
||||
static constexpr size_t kHeaderSize = 32;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "db/blob/blob_log_sequential_reader.h"
|
||||
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -29,8 +29,9 @@ Status BlobLogSequentialReader::ReadSlice(uint64_t size, Slice* slice,
|
||||
|
||||
StopWatch read_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
|
||||
// TODO: rate limit `BlobLogSequentialReader` reads (it appears unused?)
|
||||
Status s = file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size),
|
||||
slice, buf, nullptr);
|
||||
Status s =
|
||||
file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size), slice,
|
||||
buf, nullptr, Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
next_byte_ += size;
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
+53
-24
@@ -13,7 +13,7 @@
|
||||
#include "db/blob/blob_contents.h"
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "table/get_context.h"
|
||||
#include "table/multiget_context.h"
|
||||
@@ -30,14 +30,16 @@ BlobSource::BlobSource(const ImmutableOptions* immutable_options,
|
||||
blob_file_cache_(blob_file_cache),
|
||||
blob_cache_(immutable_options->blob_cache),
|
||||
lowest_used_cache_tier_(immutable_options->lowest_used_cache_tier) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto bbto =
|
||||
immutable_options->table_factory->GetOptions<BlockBasedTableOptions>();
|
||||
if (bbto &&
|
||||
bbto->cache_usage_options.options_overrides.at(CacheEntryRole::kBlobCache)
|
||||
.charged == CacheEntryRoleOptions::Decision::kEnabled) {
|
||||
blob_cache_ = SharedCacheInterface{std::make_shared<ChargedCache>(
|
||||
immutable_options->blob_cache, bbto->block_cache)};
|
||||
blob_cache_ = std::make_shared<ChargedCache>(immutable_options->blob_cache,
|
||||
bbto->block_cache);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
BlobSource::~BlobSource() = default;
|
||||
@@ -80,8 +82,9 @@ Status BlobSource::PutBlobIntoCache(
|
||||
assert(cached_blob);
|
||||
assert(cached_blob->IsEmpty());
|
||||
|
||||
TypedHandle* cache_handle = nullptr;
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
const Status s = InsertEntryIntoCache(cache_key, blob->get(),
|
||||
(*blob)->ApproximateMemoryUsage(),
|
||||
&cache_handle, Cache::Priority::BOTTOM);
|
||||
if (s.ok()) {
|
||||
blob->release();
|
||||
@@ -103,10 +106,26 @@ Status BlobSource::PutBlobIntoCache(
|
||||
return s;
|
||||
}
|
||||
|
||||
BlobSource::TypedHandle* BlobSource::GetEntryFromCache(const Slice& key) const {
|
||||
return blob_cache_.LookupFull(key, nullptr /* context */,
|
||||
Cache::Priority::BOTTOM, statistics_,
|
||||
lowest_used_cache_tier_);
|
||||
Cache::Handle* BlobSource::GetEntryFromCache(const Slice& key) const {
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
|
||||
if (lowest_used_cache_tier_ == CacheTier::kNonVolatileBlockTier) {
|
||||
Cache::CreateCallback create_cb =
|
||||
[allocator = blob_cache_->memory_allocator()](
|
||||
const void* buf, size_t size, void** out_obj,
|
||||
size_t* charge) -> Status {
|
||||
return BlobContents::CreateCallback(AllocateBlock(size, allocator), buf,
|
||||
size, out_obj, charge);
|
||||
};
|
||||
|
||||
cache_handle = blob_cache_->Lookup(key, BlobContents::GetCacheItemHelper(),
|
||||
create_cb, Cache::Priority::BOTTOM,
|
||||
true /* wait_for_cache */, statistics_);
|
||||
} else {
|
||||
cache_handle = blob_cache_->Lookup(key, statistics_);
|
||||
}
|
||||
|
||||
return cache_handle;
|
||||
}
|
||||
|
||||
void BlobSource::PinCachedBlob(CacheHandleGuard<BlobContents>* cached_blob,
|
||||
@@ -147,11 +166,24 @@ void BlobSource::PinOwnedBlob(std::unique_ptr<BlobContents>* owned_blob,
|
||||
}
|
||||
|
||||
Status BlobSource::InsertEntryIntoCache(const Slice& key, BlobContents* value,
|
||||
TypedHandle** cache_handle,
|
||||
size_t charge,
|
||||
Cache::Handle** cache_handle,
|
||||
Cache::Priority priority) const {
|
||||
return blob_cache_.InsertFull(key, value, value->ApproximateMemoryUsage(),
|
||||
cache_handle, priority,
|
||||
lowest_used_cache_tier_);
|
||||
Status s;
|
||||
|
||||
Cache::CacheItemHelper* const cache_item_helper =
|
||||
BlobContents::GetCacheItemHelper();
|
||||
assert(cache_item_helper);
|
||||
|
||||
if (lowest_used_cache_tier_ == CacheTier::kNonVolatileBlockTier) {
|
||||
s = blob_cache_->Insert(key, value, cache_item_helper, charge, cache_handle,
|
||||
priority);
|
||||
} else {
|
||||
s = blob_cache_->Insert(key, value, charge, cache_item_helper->del_cb,
|
||||
cache_handle, priority);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
@@ -209,8 +241,7 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
|
||||
{
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader);
|
||||
s = blob_file_cache_->GetBlobFileReader(file_number, &blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -221,10 +252,9 @@ Status BlobSource::GetBlob(const ReadOptions& read_options,
|
||||
return Status::Corruption("Compression type mismatch when reading blob");
|
||||
}
|
||||
|
||||
MemoryAllocator* const allocator =
|
||||
(blob_cache_ && read_options.fill_cache)
|
||||
? blob_cache_.get()->memory_allocator()
|
||||
: nullptr;
|
||||
MemoryAllocator* const allocator = (blob_cache_ && read_options.fill_cache)
|
||||
? blob_cache_->memory_allocator()
|
||||
: nullptr;
|
||||
|
||||
uint64_t read_size = 0;
|
||||
s = blob_file_reader.GetValue()->GetBlob(
|
||||
@@ -373,8 +403,8 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
}
|
||||
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
Status s = blob_file_cache_->GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader);
|
||||
Status s =
|
||||
blob_file_cache_->GetBlobFileReader(file_number, &blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
for (size_t i = 0; i < _blob_reqs.size(); ++i) {
|
||||
BlobReadRequest* const req = _blob_reqs[i].first;
|
||||
@@ -388,10 +418,9 @@ void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
|
||||
assert(blob_file_reader.GetValue());
|
||||
|
||||
MemoryAllocator* const allocator =
|
||||
(blob_cache_ && read_options.fill_cache)
|
||||
? blob_cache_.get()->memory_allocator()
|
||||
: nullptr;
|
||||
MemoryAllocator* const allocator = (blob_cache_ && read_options.fill_cache)
|
||||
? blob_cache_->memory_allocator()
|
||||
: nullptr;
|
||||
|
||||
blob_file_reader.GetValue()->MultiGetBlob(read_options, allocator,
|
||||
_blob_reqs, &_bytes_read);
|
||||
|
||||
+7
-15
@@ -8,9 +8,8 @@
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "cache/cache_key.h"
|
||||
#include "cache/typed_cache.h"
|
||||
#include "db/blob/blob_contents.h"
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_read_request.h"
|
||||
#include "rocksdb/cache.h"
|
||||
@@ -24,6 +23,7 @@ struct ImmutableOptions;
|
||||
class Status;
|
||||
class FilePrefetchBuffer;
|
||||
class Slice;
|
||||
class BlobContents;
|
||||
|
||||
// BlobSource is a class that provides universal access to blobs, regardless of
|
||||
// whether they are in the blob cache, secondary cache, or (remote) storage.
|
||||
@@ -95,9 +95,9 @@ class BlobSource {
|
||||
uint64_t* bytes_read);
|
||||
|
||||
inline Status GetBlobFileReader(
|
||||
const ReadOptions& read_options, uint64_t blob_file_number,
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
return blob_file_cache_->GetBlobFileReader(read_options, blob_file_number,
|
||||
return blob_file_cache_->GetBlobFileReader(blob_file_number,
|
||||
blob_file_reader);
|
||||
}
|
||||
|
||||
@@ -106,14 +106,6 @@ class BlobSource {
|
||||
bool TEST_BlobInCache(uint64_t file_number, uint64_t file_size,
|
||||
uint64_t offset, size_t* charge = nullptr) const;
|
||||
|
||||
// For TypedSharedCacheInterface
|
||||
void Create(BlobContents** out, const char* buf, size_t size,
|
||||
MemoryAllocator* alloc);
|
||||
|
||||
using SharedCacheInterface =
|
||||
FullTypedSharedCacheInterface<BlobContents, BlobContentsCreator>;
|
||||
using TypedHandle = SharedCacheInterface::TypedHandle;
|
||||
|
||||
private:
|
||||
Status GetBlobFromCache(const Slice& cache_key,
|
||||
CacheHandleGuard<BlobContents>* cached_blob) const;
|
||||
@@ -128,10 +120,10 @@ class BlobSource {
|
||||
static void PinOwnedBlob(std::unique_ptr<BlobContents>* owned_blob,
|
||||
PinnableSlice* value);
|
||||
|
||||
TypedHandle* GetEntryFromCache(const Slice& key) const;
|
||||
Cache::Handle* GetEntryFromCache(const Slice& key) const;
|
||||
|
||||
Status InsertEntryIntoCache(const Slice& key, BlobContents* value,
|
||||
TypedHandle** cache_handle,
|
||||
size_t charge, Cache::Handle** cache_handle,
|
||||
Cache::Priority priority) const;
|
||||
|
||||
inline CacheKey GetCacheKey(uint64_t file_number, uint64_t /*file_size*/,
|
||||
@@ -149,7 +141,7 @@ class BlobSource {
|
||||
BlobFileCache* blob_file_cache_;
|
||||
|
||||
// A cache to store uncompressed blobs.
|
||||
mutable SharedCacheInterface blob_cache_;
|
||||
std::shared_ptr<Cache> blob_cache_;
|
||||
|
||||
// The control option of how the cache tiers will be used. Currently rocksdb
|
||||
// support block/blob cache (volatile tier) and secondary cache (this tier
|
||||
|
||||
+49
-36
@@ -76,7 +76,7 @@ void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression, opts);
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression, sample_for_compression);
|
||||
@@ -517,8 +517,7 @@ TEST_F(BlobSourceTest, GetCompressedBlobs) {
|
||||
compression, blob_offsets, blob_sizes);
|
||||
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(read_options, file_number,
|
||||
&blob_file_reader));
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(file_number, &blob_file_reader));
|
||||
ASSERT_NE(blob_file_reader.GetValue(), nullptr);
|
||||
|
||||
const uint64_t file_size = blob_file_reader.GetValue()->GetFileSize();
|
||||
@@ -1140,18 +1139,26 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
blob_file_cache.get());
|
||||
|
||||
CacheHandleGuard<BlobFileReader> file_reader;
|
||||
ReadOptions read_options;
|
||||
ASSERT_OK(
|
||||
blob_source.GetBlobFileReader(read_options, file_number, &file_reader));
|
||||
ASSERT_OK(blob_source.GetBlobFileReader(file_number, &file_reader));
|
||||
ASSERT_NE(file_reader.GetValue(), nullptr);
|
||||
const uint64_t file_size = file_reader.GetValue()->GetFileSize();
|
||||
ASSERT_EQ(file_reader.GetValue()->GetCompressionType(), kNoCompression);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
auto blob_cache = options_.blob_cache;
|
||||
auto secondary_cache = lru_cache_opts_.secondary_cache;
|
||||
|
||||
Cache::CreateCallback create_cb = [](const void* buf, size_t size,
|
||||
void** out_obj,
|
||||
size_t* charge) -> Status {
|
||||
CacheAllocationPtr allocation(new char[size]);
|
||||
|
||||
return BlobContents::CreateCallback(std::move(allocation), buf, size,
|
||||
out_obj, charge);
|
||||
};
|
||||
|
||||
{
|
||||
// GetBlob
|
||||
std::vector<PinnableSlice> values(keys.size());
|
||||
@@ -1212,16 +1219,15 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
{
|
||||
CacheKey cache_key = base_cache_key.WithOffset(blob_offsets[0]);
|
||||
const Slice key0 = cache_key.AsSlice();
|
||||
auto handle0 = blob_cache->BasicLookup(key0, statistics);
|
||||
auto handle0 = blob_cache->Lookup(key0, statistics);
|
||||
ASSERT_EQ(handle0, nullptr);
|
||||
|
||||
// key0's item should be in the secondary cache.
|
||||
bool kept_in_sec_cache = false;
|
||||
auto sec_handle0 = secondary_cache->Lookup(
|
||||
key0, BlobSource::SharedCacheInterface::GetFullHelper(),
|
||||
/*context*/ nullptr, true,
|
||||
/*advise_erase=*/true, kept_in_sec_cache);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
bool is_in_sec_cache = false;
|
||||
auto sec_handle0 =
|
||||
secondary_cache->Lookup(key0, create_cb, true,
|
||||
/*advise_erase=*/true, is_in_sec_cache);
|
||||
ASSERT_FALSE(is_in_sec_cache);
|
||||
ASSERT_NE(sec_handle0, nullptr);
|
||||
ASSERT_TRUE(sec_handle0->IsReady());
|
||||
auto value = static_cast<BlobContents*>(sec_handle0->Value());
|
||||
@@ -1240,16 +1246,15 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
{
|
||||
CacheKey cache_key = base_cache_key.WithOffset(blob_offsets[1]);
|
||||
const Slice key1 = cache_key.AsSlice();
|
||||
auto handle1 = blob_cache->BasicLookup(key1, statistics);
|
||||
auto handle1 = blob_cache->Lookup(key1, statistics);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
blob_cache->Release(handle1);
|
||||
|
||||
bool kept_in_sec_cache = false;
|
||||
auto sec_handle1 = secondary_cache->Lookup(
|
||||
key1, BlobSource::SharedCacheInterface::GetFullHelper(),
|
||||
/*context*/ nullptr, true,
|
||||
/*advise_erase=*/true, kept_in_sec_cache);
|
||||
ASSERT_FALSE(kept_in_sec_cache);
|
||||
bool is_in_sec_cache = false;
|
||||
auto sec_handle1 =
|
||||
secondary_cache->Lookup(key1, create_cb, true,
|
||||
/*advise_erase=*/true, is_in_sec_cache);
|
||||
ASSERT_FALSE(is_in_sec_cache);
|
||||
ASSERT_EQ(sec_handle1, nullptr);
|
||||
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(file_number, file_size,
|
||||
@@ -1271,7 +1276,7 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
// key0 should be in the primary cache.
|
||||
CacheKey cache_key0 = base_cache_key.WithOffset(blob_offsets[0]);
|
||||
const Slice key0 = cache_key0.AsSlice();
|
||||
auto handle0 = blob_cache->BasicLookup(key0, statistics);
|
||||
auto handle0 = blob_cache->Lookup(key0, statistics);
|
||||
ASSERT_NE(handle0, nullptr);
|
||||
auto value = static_cast<BlobContents*>(blob_cache->Value(handle0));
|
||||
ASSERT_NE(value, nullptr);
|
||||
@@ -1281,12 +1286,12 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
// key1 is not in the primary cache and is in the secondary cache.
|
||||
CacheKey cache_key1 = base_cache_key.WithOffset(blob_offsets[1]);
|
||||
const Slice key1 = cache_key1.AsSlice();
|
||||
auto handle1 = blob_cache->BasicLookup(key1, statistics);
|
||||
auto handle1 = blob_cache->Lookup(key1, statistics);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
|
||||
// erase key0 from the primary cache.
|
||||
blob_cache->Erase(key0);
|
||||
handle0 = blob_cache->BasicLookup(key0, statistics);
|
||||
handle0 = blob_cache->Lookup(key0, statistics);
|
||||
ASSERT_EQ(handle0, nullptr);
|
||||
|
||||
// key1 promotion should succeed due to the primary cache being empty. we
|
||||
@@ -1302,7 +1307,7 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
// in the secondary cache. So, the primary cache's Lookup() without
|
||||
// secondary cache support cannot see it. (NOTE: The dummy handle used
|
||||
// to be a leaky abstraction but not anymore.)
|
||||
handle1 = blob_cache->BasicLookup(key1, statistics);
|
||||
handle1 = blob_cache->Lookup(key1, statistics);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
|
||||
// But after another access, it is promoted to primary cache
|
||||
@@ -1310,7 +1315,7 @@ TEST_F(BlobSecondaryCacheTest, GetBlobsFromSecondaryCache) {
|
||||
blob_offsets[1]));
|
||||
|
||||
// And Lookup() can find it (without secondary cache support)
|
||||
handle1 = blob_cache->BasicLookup(key1, statistics);
|
||||
handle1 = blob_cache->Lookup(key1, statistics);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
ASSERT_NE(blob_cache->Value(handle1), nullptr);
|
||||
blob_cache->Release(handle1);
|
||||
@@ -1374,7 +1379,7 @@ class BlobSourceCacheReservationTest : public DBTestBase {
|
||||
|
||||
static constexpr std::size_t kSizeDummyEntry = CacheReservationManagerImpl<
|
||||
CacheEntryRole::kBlobCache>::GetDummyEntrySize();
|
||||
static constexpr std::size_t kCacheCapacity = 2 * kSizeDummyEntry;
|
||||
static constexpr std::size_t kCacheCapacity = 1 * kSizeDummyEntry;
|
||||
static constexpr int kNumShardBits = 0; // 2^0 shard
|
||||
|
||||
static constexpr uint32_t kColumnFamilyId = 1;
|
||||
@@ -1393,6 +1398,7 @@ class BlobSourceCacheReservationTest : public DBTestBase {
|
||||
std::string db_session_id_;
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
options_.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(
|
||||
@@ -1507,10 +1513,11 @@ TEST_F(BlobSourceCacheReservationTest, SimpleCacheReservation) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservationOnFullCache) {
|
||||
options_.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(
|
||||
env_, "BlobSourceCacheReservationTest_IncreaseCacheReservation"),
|
||||
env_,
|
||||
"BlobSourceCacheReservationTest_IncreaseCacheReservationOnFullCache"),
|
||||
0);
|
||||
|
||||
GenerateKeysAndBlobs();
|
||||
@@ -1518,7 +1525,7 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
DestroyAndReopen(options_);
|
||||
|
||||
ImmutableOptions immutable_options(options_);
|
||||
constexpr size_t blob_size = 24 << 10; // 24KB
|
||||
constexpr size_t blob_size = kSizeDummyEntry / (kNumBlobs / 2);
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
blob_file_size_ -= blobs_[i].size(); // old blob size
|
||||
blob_strs_[i].resize(blob_size, '@');
|
||||
@@ -1576,6 +1583,11 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
|
||||
std::vector<PinnableSlice> values(keys_.size());
|
||||
|
||||
// Since we resized each blob to be kSizeDummyEntry / (num_blobs / 2), we
|
||||
// can't fit all the blobs in the cache at the same time, which means we
|
||||
// should observe cache evictions once we reach the cache's capacity.
|
||||
// Due to the overhead of the cache and the BlobContents objects, as well as
|
||||
// jemalloc bin sizes, this happens after inserting seven blobs.
|
||||
uint64_t blob_bytes = 0;
|
||||
for (size_t i = 0; i < kNumBlobs; ++i) {
|
||||
ASSERT_OK(blob_source.GetBlob(
|
||||
@@ -1586,21 +1598,22 @@ TEST_F(BlobSourceCacheReservationTest, IncreaseCacheReservation) {
|
||||
// Release cache handle
|
||||
values[i].Reset();
|
||||
|
||||
size_t charge = 0;
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(kBlobFileNumber, blob_file_size_,
|
||||
blob_offsets[i], &charge));
|
||||
if (i < kNumBlobs / 2 - 1) {
|
||||
size_t charge = 0;
|
||||
ASSERT_TRUE(blob_source.TEST_BlobInCache(
|
||||
kBlobFileNumber, blob_file_size_, blob_offsets[i], &charge));
|
||||
|
||||
blob_bytes += charge;
|
||||
blob_bytes += charge;
|
||||
}
|
||||
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(),
|
||||
(blob_bytes <= kSizeDummyEntry) ? kSizeDummyEntry
|
||||
: (2 * kSizeDummyEntry));
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalReservedCacheSize(), kSizeDummyEntry);
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(), blob_bytes);
|
||||
ASSERT_EQ(cache_res_mgr->GetTotalMemoryUsed(),
|
||||
options_.blob_cache->GetUsage());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/db_with_timestamp_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
@@ -585,6 +584,7 @@ TEST_F(DBBlobBasicTest, MultiGetBlobsFromCache) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, MultiGetWithDirectIO) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
@@ -773,12 +773,13 @@ TEST_F(DBBlobBasicTest, MultiGetWithDirectIO) {
|
||||
ASSERT_EQ(values[2], second_blob);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobsFromMultipleFiles) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 2 << 20; // 2MB
|
||||
co.capacity = 2 << 20; // 2MB
|
||||
co.num_shard_bits = 2;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
auto backing_cache = NewLRUCache(co);
|
||||
@@ -1061,6 +1062,7 @@ TEST_F(DBBlobBasicTest, GetBlob_IndexWithInvalidFileNumber) {
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, GenerateIOTracing) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -1115,6 +1117,7 @@ TEST_F(DBBlobBasicTest, GenerateIOTracing) {
|
||||
ASSERT_GT(blob_files_op_count, 2);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, BestEffortsRecovery_MissingNewestBlobFile) {
|
||||
Options options = GetDefaultOptions();
|
||||
@@ -1216,6 +1219,7 @@ TEST_F(DBBlobBasicTest, MultiGetMergeBlobWithPut) {
|
||||
ASSERT_EQ(values[2], "v2_0");
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, Properties) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -1378,6 +1382,7 @@ TEST_F(DBBlobBasicTest, PropertiesMultiVersion) {
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size + BlobLogFooter::kSize));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
@@ -1627,6 +1632,7 @@ TEST_F(DBBlobBasicTest, WarmCacheWithBlobsDuringFlush) {
|
||||
options.statistics->getTickerCount(BLOB_DB_CACHE_ADD));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, DynamicallyWarmCacheDuringFlush) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
@@ -1694,6 +1700,7 @@ TEST_F(DBBlobBasicTest, DynamicallyWarmCacheDuringFlush) {
|
||||
/*end=*/nullptr));
|
||||
EXPECT_EQ(0, options.statistics->getTickerCount(BLOB_DB_CACHE_ADD));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, WarmCacheWithBlobsSecondary) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
@@ -1772,461 +1779,6 @@ TEST_F(DBBlobBasicTest, WarmCacheWithBlobsSecondary) {
|
||||
1);
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetEntityBlob) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
constexpr char other_key[] = "other_key";
|
||||
constexpr char other_blob_value[] = "other_blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
ASSERT_OK(Put(other_key, other_blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
WideColumns expected_columns{{kDefaultWideColumnName, blob_value}};
|
||||
WideColumns other_expected_columns{
|
||||
{kDefaultWideColumnName, other_blob_value}};
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(), key,
|
||||
&result));
|
||||
ASSERT_EQ(result.columns(), expected_columns);
|
||||
}
|
||||
|
||||
{
|
||||
PinnableWideColumns result;
|
||||
ASSERT_OK(db_->GetEntity(ReadOptions(), db_->DefaultColumnFamily(),
|
||||
other_key, &result));
|
||||
|
||||
ASSERT_EQ(result.columns(), other_expected_columns);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr size_t num_keys = 2;
|
||||
|
||||
std::array<Slice, num_keys> keys{{key, other_key}};
|
||||
std::array<PinnableWideColumns, num_keys> results;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGetEntity(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
&keys[0], &results[0], &statuses[0]);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(results[0].columns(), expected_columns);
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(results[1].columns(), other_expected_columns);
|
||||
}
|
||||
}
|
||||
|
||||
class DBBlobWithTimestampTest : public DBBasicTestWithTimestampBase {
|
||||
protected:
|
||||
DBBlobWithTimestampTest()
|
||||
: DBBasicTestWithTimestampBase("db_blob_with_timestamp_test") {}
|
||||
};
|
||||
|
||||
TEST_F(DBBlobWithTimestampTest, GetBlob) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions write_opts;
|
||||
const std::string ts = Timestamp(1, 0);
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, key, ts, blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
const std::string read_ts = Timestamp(2, 0);
|
||||
Slice read_ts_slice(read_ts);
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(read_opts, key, &value));
|
||||
ASSERT_EQ(value, blob_value);
|
||||
}
|
||||
|
||||
TEST_F(DBBlobWithTimestampTest, MultiGetBlobs) {
|
||||
constexpr size_t min_blob_size = 6;
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = min_blob_size;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Put then retrieve three key-values. The first value is below the size limit
|
||||
// and is thus stored inline; the other two are stored separately as blobs.
|
||||
constexpr size_t num_keys = 3;
|
||||
|
||||
constexpr char first_key[] = "first_key";
|
||||
constexpr char first_value[] = "short";
|
||||
static_assert(sizeof(first_value) - 1 < min_blob_size,
|
||||
"first_value too long to be inlined");
|
||||
|
||||
DestroyAndReopen(options);
|
||||
WriteOptions write_opts;
|
||||
const std::string ts = Timestamp(1, 0);
|
||||
ASSERT_OK(db_->Put(write_opts, first_key, ts, first_value));
|
||||
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char second_value[] = "long_value";
|
||||
static_assert(sizeof(second_value) - 1 >= min_blob_size,
|
||||
"second_value too short to be stored as blob");
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, second_key, ts, second_value));
|
||||
|
||||
constexpr char third_key[] = "third_key";
|
||||
constexpr char third_value[] = "other_long_value";
|
||||
static_assert(sizeof(third_value) - 1 >= min_blob_size,
|
||||
"third_value too short to be stored as blob");
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, third_key, ts, third_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
const std::string read_ts = Timestamp(2, 0);
|
||||
Slice read_ts_slice(read_ts);
|
||||
read_options.timestamp = &read_ts_slice;
|
||||
std::array<Slice, num_keys> keys{{first_key, second_key, third_key}};
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(read_options, db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], first_value);
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(values[1], second_value);
|
||||
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], third_value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobWithTimestampTest, GetMergeBlobWithPut) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions write_opts;
|
||||
const std::string ts = Timestamp(1, 0);
|
||||
ASSERT_OK(db_->Put(write_opts, "Key1", ts, "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(
|
||||
db_->Merge(write_opts, db_->DefaultColumnFamily(), "Key1", ts, "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(
|
||||
db_->Merge(write_opts, db_->DefaultColumnFamily(), "Key1", ts, "v3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::string value;
|
||||
const std::string read_ts = Timestamp(2, 0);
|
||||
Slice read_ts_slice(read_ts);
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
ASSERT_OK(db_->Get(read_opts, "Key1", &value));
|
||||
ASSERT_EQ(value, "v1,v2,v3");
|
||||
}
|
||||
|
||||
TEST_F(DBBlobWithTimestampTest, MultiGetMergeBlobWithPut) {
|
||||
constexpr size_t num_keys = 3;
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
WriteOptions write_opts;
|
||||
const std::string ts = Timestamp(1, 0);
|
||||
|
||||
ASSERT_OK(db_->Put(write_opts, "Key0", ts, "v0_0"));
|
||||
ASSERT_OK(db_->Put(write_opts, "Key1", ts, "v1_0"));
|
||||
ASSERT_OK(db_->Put(write_opts, "Key2", ts, "v2_0"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(
|
||||
db_->Merge(write_opts, db_->DefaultColumnFamily(), "Key0", ts, "v0_1"));
|
||||
ASSERT_OK(
|
||||
db_->Merge(write_opts, db_->DefaultColumnFamily(), "Key1", ts, "v1_1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(
|
||||
db_->Merge(write_opts, db_->DefaultColumnFamily(), "Key0", ts, "v0_2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
const std::string read_ts = Timestamp(2, 0);
|
||||
Slice read_ts_slice(read_ts);
|
||||
ReadOptions read_opts;
|
||||
read_opts.timestamp = &read_ts_slice;
|
||||
std::array<Slice, num_keys> keys{{"Key0", "Key1", "Key2"}};
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(read_opts, db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], "v0_0,v0_1,v0_2");
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(values[1], "v1_0,v1_1");
|
||||
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], "v2_0");
|
||||
}
|
||||
|
||||
TEST_F(DBBlobWithTimestampTest, IterateBlobs) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.create_if_missing = true;
|
||||
const size_t kTimestampSize = Timestamp(0, 0).size();
|
||||
TestComparator test_cmp(kTimestampSize);
|
||||
options.comparator = &test_cmp;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int num_blobs = 5;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> blobs;
|
||||
|
||||
WriteOptions write_opts;
|
||||
std::vector<std::string> write_timestamps = {Timestamp(1, 0),
|
||||
Timestamp(2, 0)};
|
||||
|
||||
// For each key in ["key0", ... "keyi", ...], write two versions:
|
||||
// Timestamp(1, 0), "blobi0"
|
||||
// Timestamp(2, 0), "blobi1"
|
||||
for (int i = 0; i < num_blobs; i++) {
|
||||
keys.push_back("key" + std::to_string(i));
|
||||
blobs.push_back("blob" + std::to_string(i));
|
||||
for (size_t j = 0; j < write_timestamps.size(); j++) {
|
||||
ASSERT_OK(db_->Put(write_opts, keys[i], write_timestamps[j],
|
||||
blobs[i] + std::to_string(j)));
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
std::vector<std::string> read_timestamps = {Timestamp(0, 0), Timestamp(3, 0)};
|
||||
Slice ts_upper_bound(read_timestamps[1]);
|
||||
read_options.timestamp = &ts_upper_bound;
|
||||
|
||||
auto check_iter_entry =
|
||||
[](const Iterator* iter, const std::string& expected_key,
|
||||
const std::string& expected_ts, const std::string& expected_value,
|
||||
bool key_is_internal = true) {
|
||||
ASSERT_OK(iter->status());
|
||||
if (key_is_internal) {
|
||||
std::string expected_ukey_and_ts;
|
||||
expected_ukey_and_ts.assign(expected_key.data(), expected_key.size());
|
||||
expected_ukey_and_ts.append(expected_ts.data(), expected_ts.size());
|
||||
|
||||
ParsedInternalKey parsed_ikey;
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &parsed_ikey,
|
||||
true /* log_err_key */));
|
||||
ASSERT_EQ(parsed_ikey.user_key, expected_ukey_and_ts);
|
||||
} else {
|
||||
ASSERT_EQ(iter->key(), expected_key);
|
||||
}
|
||||
ASSERT_EQ(iter->timestamp(), expected_ts);
|
||||
ASSERT_EQ(iter->value(), expected_value);
|
||||
};
|
||||
|
||||
// Forward iterating one version of each key, get in this order:
|
||||
// [("key0", Timestamp(2, 0), "blob01"),
|
||||
// ("key1", Timestamp(2, 0), "blob11")...]
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToFirst();
|
||||
for (int i = 0; i < num_blobs; i++) {
|
||||
check_iter_entry(iter.get(), keys[i], write_timestamps[1],
|
||||
blobs[i] + std::to_string(1), /*key_is_internal*/ false);
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
|
||||
// Forward iteration, then reverse to backward.
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToFirst();
|
||||
for (int i = 0; i < num_blobs * 2 - 1; i++) {
|
||||
if (i < num_blobs) {
|
||||
check_iter_entry(iter.get(), keys[i], write_timestamps[1],
|
||||
blobs[i] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
if (i != num_blobs - 1) {
|
||||
iter->Next();
|
||||
}
|
||||
} else {
|
||||
if (i != num_blobs) {
|
||||
check_iter_entry(iter.get(), keys[num_blobs * 2 - 1 - i],
|
||||
write_timestamps[1],
|
||||
blobs[num_blobs * 2 - 1 - i] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
}
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iterating one versions of each key, get in this order:
|
||||
// [("key4", Timestamp(2, 0), "blob41"),
|
||||
// ("key3", Timestamp(2, 0), "blob31")...]
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
for (int i = 0; i < num_blobs; i++) {
|
||||
check_iter_entry(iter.get(), keys[num_blobs - 1 - i], write_timestamps[1],
|
||||
blobs[num_blobs - 1 - i] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iteration, then reverse to forward.
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
for (int i = 0; i < num_blobs * 2 - 1; i++) {
|
||||
if (i < num_blobs) {
|
||||
check_iter_entry(iter.get(), keys[num_blobs - 1 - i],
|
||||
write_timestamps[1],
|
||||
blobs[num_blobs - 1 - i] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
if (i != num_blobs - 1) {
|
||||
iter->Prev();
|
||||
}
|
||||
} else {
|
||||
if (i != num_blobs) {
|
||||
check_iter_entry(iter.get(), keys[i - num_blobs], write_timestamps[1],
|
||||
blobs[i - num_blobs] + std::to_string(1),
|
||||
/*key_is_internal*/ false);
|
||||
}
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Slice ts_lower_bound(read_timestamps[0]);
|
||||
read_options.iter_start_ts = &ts_lower_bound;
|
||||
// Forward iterating multiple versions of the same key, get in this order:
|
||||
// [("key0", Timestamp(2, 0), "blob01"),
|
||||
// ("key0", Timestamp(1, 0), "blob00"),
|
||||
// ("key1", Timestamp(2, 0), "blob11")...]
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToFirst();
|
||||
for (int i = 0; i < num_blobs; i++) {
|
||||
for (size_t j = write_timestamps.size(); j > 0; --j) {
|
||||
check_iter_entry(iter.get(), keys[i], write_timestamps[j - 1],
|
||||
blobs[i] + std::to_string(j - 1));
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iterating multiple versions of the same key, get in this order:
|
||||
// [("key4", Timestamp(1, 0), "blob00"),
|
||||
// ("key4", Timestamp(2, 0), "blob01"),
|
||||
// ("key3", Timestamp(1, 0), "blob10")...]
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
for (int i = num_blobs; i > 0; i--) {
|
||||
for (size_t j = 0; j < write_timestamps.size(); j++) {
|
||||
check_iter_entry(iter.get(), keys[i - 1], write_timestamps[j],
|
||||
blobs[i - 1] + std::to_string(j));
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int upper_bound_idx = num_blobs - 2;
|
||||
int lower_bound_idx = 1;
|
||||
Slice upper_bound_slice(keys[upper_bound_idx]);
|
||||
Slice lower_bound_slice(keys[lower_bound_idx]);
|
||||
read_options.iterate_upper_bound = &upper_bound_slice;
|
||||
read_options.iterate_lower_bound = &lower_bound_slice;
|
||||
|
||||
// Forward iteration with upper and lower bound.
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToFirst();
|
||||
for (int i = lower_bound_idx; i < upper_bound_idx; i++) {
|
||||
for (size_t j = write_timestamps.size(); j > 0; --j) {
|
||||
check_iter_entry(iter.get(), keys[i], write_timestamps[j - 1],
|
||||
blobs[i] + std::to_string(j - 1));
|
||||
iter->Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backward iteration with upper and lower bound.
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
iter->SeekToLast();
|
||||
for (int i = upper_bound_idx; i > lower_bound_idx; i--) {
|
||||
for (size_t j = 0; j < write_timestamps.size(); j++) {
|
||||
check_iter_entry(iter.get(), keys[i - 1], write_timestamps[j],
|
||||
blobs[i - 1] + std::to_string(j));
|
||||
iter->Prev();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -16,6 +16,7 @@ class DBBlobCompactionTest : public DBTestBase {
|
||||
explicit DBBlobCompactionTest()
|
||||
: DBTestBase("db_blob_compaction_test", /*env_do_fsync=*/false) {}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const std::vector<InternalStats::CompactionStats>& GetCompactionStats() {
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
assert(versions);
|
||||
@@ -29,6 +30,7 @@ class DBBlobCompactionTest : public DBTestBase {
|
||||
|
||||
return internal_stats->TEST_GetCompactionStats();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -248,6 +250,7 @@ TEST_F(DBBlobCompactionTest, FilterByKeyLength) {
|
||||
ASSERT_OK(db_->Get(ReadOptions(), long_key, &value));
|
||||
ASSERT_EQ("value", value);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
@@ -255,6 +258,7 @@ TEST_F(DBBlobCompactionTest, FilterByKeyLength) {
|
||||
// this involves neither reading nor writing blobs
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
@@ -295,6 +299,7 @@ TEST_F(DBBlobCompactionTest, FilterByValueLength) {
|
||||
ASSERT_EQ(long_value, value);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
@@ -302,10 +307,12 @@ TEST_F(DBBlobCompactionTest, FilterByValueLength) {
|
||||
// this involves reading but not writing blobs
|
||||
ASSERT_GT(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobCompactionTest, BlobCompactWithStartingLevel) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
@@ -381,6 +388,7 @@ TEST_F(DBBlobCompactionTest, BlobCompactWithStartingLevel) {
|
||||
|
||||
Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
@@ -405,6 +413,7 @@ TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
ASSERT_EQ(new_blob_value, Get(key));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
@@ -412,6 +421,7 @@ TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
// this involves writing but not reading blobs
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_GT(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
@@ -530,6 +540,7 @@ TEST_F(DBBlobCompactionTest, CompactionFilter) {
|
||||
ASSERT_EQ(kv.second + std::string(padding), Get(kv.first));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
@@ -537,6 +548,7 @@ TEST_F(DBBlobCompactionTest, CompactionFilter) {
|
||||
// this involves reading and writing blobs
|
||||
ASSERT_GT(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_GT(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
@@ -594,6 +606,7 @@ TEST_F(DBBlobCompactionTest, CompactionFilterReadBlobAndKeep) {
|
||||
/*end=*/nullptr));
|
||||
ASSERT_EQ(blob_files, GetBlobFileNumbers());
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
@@ -601,6 +614,7 @@ TEST_F(DBBlobCompactionTest, CompactionFilterReadBlobAndKeep) {
|
||||
// this involves reading but not writing blobs
|
||||
ASSERT_GT(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class DBBlobCorruptionTest : public DBTestBase {
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobCorruptionTest, VerifyWholeBlobFileChecksum) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -70,6 +71,7 @@ TEST_F(DBBlobCorruptionTest, VerifyWholeBlobFileChecksum) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -131,7 +131,9 @@ class DBBlobIndexTest : public DBTestBase {
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -457,6 +459,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Iterator with blob support and using seek.
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
cfh(), {{"max_sequential_skip_in_iterations", "0"}}));
|
||||
@@ -481,6 +484,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
for (auto* snapshot : snapshots) {
|
||||
dbfull()->ReleaseSnapshot(snapshot);
|
||||
@@ -580,10 +584,12 @@ TEST_F(DBBlobIndexTest, IntegratedBlobIterate) {
|
||||
Status expected_status;
|
||||
verify(1, expected_status, expected_value);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Test DBIter::FindValueForCurrentKeyUsingSeek flow.
|
||||
ASSERT_OK(dbfull()->SetOptions(cfh(),
|
||||
{{"max_sequential_skip_in_iterations", "0"}}));
|
||||
verify(1, expected_status, expected_value);
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+24
-63
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
@@ -57,8 +56,8 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
|
||||
const FileOptions& file_options, const ReadOptions& read_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
const FileOptions& file_options, TableCache* table_cache,
|
||||
InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, std::vector<BlobFileAddition>* blob_file_additions,
|
||||
@@ -72,9 +71,8 @@ Status BuildTable(
|
||||
int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, Env::WriteLifeTimeHint write_hint,
|
||||
const std::string* full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback, Version* version,
|
||||
uint64_t* num_input_entries, uint64_t* memtable_payload_bytes,
|
||||
uint64_t* memtable_garbage_bytes) {
|
||||
BlobFileCompletionCallback* blob_callback, uint64_t* num_input_entries,
|
||||
uint64_t* memtable_payload_bytes, uint64_t* memtable_garbage_bytes) {
|
||||
assert((tboptions.column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
tboptions.column_family_name.empty());
|
||||
@@ -108,9 +106,11 @@ Status BuildTable(
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
#ifndef ROCKSDB_LITE
|
||||
EventHelpers::NotifyTableFileCreationStarted(ioptions.listeners, dbname,
|
||||
tboptions.column_family_name,
|
||||
fname, job_id, tboptions.reason);
|
||||
#endif // !ROCKSDB_LITE
|
||||
Env* env = db_options.env;
|
||||
assert(env);
|
||||
FileSystem* fs = db_options.fs.get();
|
||||
@@ -175,10 +175,10 @@ Status BuildTable(
|
||||
builder = NewTableBuilder(tboptions, file_writer.get());
|
||||
}
|
||||
|
||||
auto ucmp = tboptions.internal_comparator.user_comparator();
|
||||
MergeHelper merge(
|
||||
env, ucmp, ioptions.merge_operator.get(), compaction_filter.get(),
|
||||
ioptions.logger, true /* internal key corruption is not ok */,
|
||||
env, tboptions.internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator.get(), compaction_filter.get(), ioptions.logger,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back(), snapshot_checker);
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
@@ -196,49 +196,32 @@ Status BuildTable(
|
||||
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
CompactionIterator c_iter(
|
||||
iter, ucmp, &merge, kMaxSequenceNumber, &snapshots,
|
||||
earliest_write_conflict_snapshot, job_snapshot, snapshot_checker, env,
|
||||
iter, tboptions.internal_comparator.user_comparator(), &merge,
|
||||
kMaxSequenceNumber, &snapshots, earliest_write_conflict_snapshot,
|
||||
job_snapshot, snapshot_checker, env,
|
||||
ShouldReportDetailedTime(env, ioptions.stats),
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors,
|
||||
ioptions.enforce_single_del_contracts,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
true /* must_count_input_entries */,
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr, db_options.info_log, full_history_ts_low);
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
const bool strip_timestamp =
|
||||
ts_sz > 0 && !ioptions.persist_user_defined_timestamps;
|
||||
|
||||
std::string key_after_flush_buf;
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
const Slice& key = c_iter.key();
|
||||
const Slice& value = c_iter.value();
|
||||
const ParsedInternalKey& ikey = c_iter.ikey();
|
||||
Slice key_after_flush = key;
|
||||
// If user defined timestamps will be stripped from user key after flush,
|
||||
// the in memory version of the key act logically the same as one with a
|
||||
// minimum timestamp. We update the timestamp here so file boundary and
|
||||
// output validator, block builder all see the effect of the stripping.
|
||||
if (strip_timestamp) {
|
||||
key_after_flush_buf.clear();
|
||||
ReplaceInternalKeyWithMinTimestamp(&key_after_flush_buf, key, ts_sz);
|
||||
key_after_flush = key_after_flush_buf;
|
||||
}
|
||||
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key_after_flush, value);
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key, value);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
builder->Add(key_after_flush, value);
|
||||
builder->Add(key, value);
|
||||
|
||||
s = meta->UpdateBoundaries(key_after_flush, value, ikey.sequence,
|
||||
ikey.type);
|
||||
s = meta->UpdateBoundaries(key, value, ikey.sequence, ikey.type);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
@@ -258,38 +241,22 @@ Status BuildTable(
|
||||
|
||||
if (s.ok()) {
|
||||
auto range_del_it = range_del_agg->NewIterator();
|
||||
Slice last_tombstone_start_user_key{};
|
||||
for (range_del_it->SeekToFirst(); range_del_it->Valid();
|
||||
range_del_it->Next()) {
|
||||
auto tombstone = range_del_it->Tombstone();
|
||||
auto kv = tombstone.Serialize();
|
||||
// TODO(yuzhangyu): handle range deletion for UDT in memtables only.
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone_end, tombstone.seq_,
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone.SerializeEndKey(),
|
||||
tombstone.seq_,
|
||||
tboptions.internal_comparator);
|
||||
if (version) {
|
||||
if (last_tombstone_start_user_key.empty() ||
|
||||
ucmp->CompareWithoutTimestamp(last_tombstone_start_user_key,
|
||||
range_del_it->start_key()) < 0) {
|
||||
SizeApproximationOptions approx_opts;
|
||||
approx_opts.files_size_error_margin = 0.1;
|
||||
meta->compensated_range_deletion_size += versions->ApproximateSize(
|
||||
approx_opts, read_options, version, kv.first.Encode(),
|
||||
tombstone_end.Encode(), 0 /* start_level */, -1 /* end_level */,
|
||||
TableReaderCaller::kFlush);
|
||||
}
|
||||
last_tombstone_start_user_key = range_del_it->start_key();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
|
||||
const bool empty = builder->IsEmpty();
|
||||
if (num_input_entries != nullptr) {
|
||||
assert(c_iter.HasNumInputEntryScanned());
|
||||
*num_input_entries =
|
||||
c_iter.NumInputEntryScanned() + num_unfragmented_tombstones;
|
||||
c_iter.num_input_entry_scanned() + num_unfragmented_tombstones;
|
||||
}
|
||||
if (!s.ok() || empty) {
|
||||
builder->Abandon();
|
||||
@@ -312,13 +279,9 @@ Status BuildTable(
|
||||
if (s.ok() && !empty) {
|
||||
uint64_t file_size = builder->FileSize();
|
||||
meta->fd.file_size = file_size;
|
||||
meta->tail_size = builder->GetTailSize();
|
||||
meta->marked_for_compaction = builder->NeedCompact();
|
||||
meta->user_defined_timestamps_persisted =
|
||||
ioptions.persist_user_defined_timestamps;
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder
|
||||
->GetTableProperties(); // refresh now that builder is finished
|
||||
tp = builder->GetTableProperties(); // refresh now that builder is finished
|
||||
if (memtable_payload_bytes != nullptr &&
|
||||
memtable_garbage_bytes != nullptr) {
|
||||
const CompactionIterationStats& ci_stats = c_iter.iter_stats();
|
||||
@@ -375,8 +338,6 @@ Status BuildTable(
|
||||
s = *io_status;
|
||||
}
|
||||
|
||||
// TODO(yuzhangyu): handle the key copy in the blob when ts should be
|
||||
// stripped.
|
||||
if (blob_file_builder) {
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
@@ -395,6 +356,7 @@ Status BuildTable(
|
||||
// here because this is a special case after we finish the table building.
|
||||
// No matter whether use_direct_io_for_flush_and_compaction is true,
|
||||
// the goal is to cache it here for further user reads.
|
||||
ReadOptions read_options;
|
||||
std::unique_ptr<InternalIterator> it(table_cache->NewIterator(
|
||||
read_options, file_options, tboptions.internal_comparator, *meta,
|
||||
nullptr /* range_del_agg */, mutable_cf_options.prefix_extractor,
|
||||
@@ -406,8 +368,7 @@ Status BuildTable(
|
||||
MaxFileSizeForL0MetaPin(mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key*/ nullptr,
|
||||
/*allow_unprepared_value*/ false,
|
||||
mutable_cf_options.block_protection_bytes_per_key));
|
||||
/*allow_unprepared_value*/ false));
|
||||
s = it->status();
|
||||
if (s.ok() && paranoid_file_checks) {
|
||||
OutputValidator file_validator(tboptions.internal_comparator,
|
||||
|
||||
+3
-4
@@ -13,7 +13,6 @@
|
||||
#include "db/range_tombstone_fragmenter.h"
|
||||
#include "db/seqno_to_time_mapping.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/version_set.h"
|
||||
#include "logging/event_logger.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
@@ -53,8 +52,8 @@ TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
extern Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
|
||||
const FileOptions& file_options, const ReadOptions& read_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
const FileOptions& file_options, TableCache* table_cache,
|
||||
InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, std::vector<BlobFileAddition>* blob_file_additions,
|
||||
@@ -71,7 +70,7 @@ extern Status BuildTable(
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const std::string* full_history_ts_low = nullptr,
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
Version* version = nullptr, uint64_t* num_input_entries = nullptr,
|
||||
uint64_t* num_input_entries = nullptr,
|
||||
uint64_t* memtable_payload_bytes = nullptr,
|
||||
uint64_t* memtable_garbage_bytes = nullptr);
|
||||
|
||||
|
||||
+186
-416
@@ -3,14 +3,17 @@
|
||||
found in the LICENSE file. See the AUTHORS file for names of contributors. */
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef ROCKSDB_LITE // Lite does not support C API
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
#ifndef OS_WIN
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -46,32 +49,32 @@ static void StartPhase(const char* name) {
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4996) // getenv security warning
|
||||
#pragma warning (disable: 4996) // getenv security warning
|
||||
#endif
|
||||
static const char* GetTempDir(void) {
|
||||
const char* ret = getenv("TEST_TMPDIR");
|
||||
if (ret == NULL || ret[0] == '\0')
|
||||
const char* ret = getenv("TEST_TMPDIR");
|
||||
if (ret == NULL || ret[0] == '\0')
|
||||
#ifdef OS_WIN
|
||||
ret = getenv("TEMP");
|
||||
ret = getenv("TEMP");
|
||||
#else
|
||||
ret = "/tmp";
|
||||
ret = "/tmp";
|
||||
#endif
|
||||
return ret;
|
||||
return ret;
|
||||
}
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#define CheckNoError(err) \
|
||||
if ((err) != NULL) { \
|
||||
#define CheckNoError(err) \
|
||||
if ((err) != NULL) { \
|
||||
fprintf(stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, phase, (err)); \
|
||||
abort(); \
|
||||
abort(); \
|
||||
}
|
||||
|
||||
#define CheckCondition(cond) \
|
||||
if (!(cond)) { \
|
||||
#define CheckCondition(cond) \
|
||||
if (!(cond)) { \
|
||||
fprintf(stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, phase, #cond); \
|
||||
abort(); \
|
||||
abort(); \
|
||||
}
|
||||
|
||||
static void CheckEqual(const char* expected, const char* v, size_t n) {
|
||||
@@ -95,15 +98,21 @@ static void Free(char** ptr) {
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckValue(char* err, const char* expected, char** actual,
|
||||
size_t actual_length) {
|
||||
static void CheckValue(
|
||||
char* err,
|
||||
const char* expected,
|
||||
char** actual,
|
||||
size_t actual_length) {
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, *actual, actual_length);
|
||||
Free(actual);
|
||||
}
|
||||
|
||||
static void CheckGet(rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
const char* key, const char* expected) {
|
||||
static void CheckGet(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key,
|
||||
const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
@@ -113,9 +122,12 @@ static void CheckGet(rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckGetCF(rocksdb_t* db, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* handle, const char* key,
|
||||
const char* expected) {
|
||||
static void CheckGetCF(
|
||||
rocksdb_t* db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* handle,
|
||||
const char* key,
|
||||
const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
@@ -162,8 +174,8 @@ static void CheckMultiGetValues(size_t num_keys, char** values,
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckIter(rocksdb_iterator_t* iter, const char* key,
|
||||
const char* val) {
|
||||
static void CheckIter(rocksdb_iterator_t* iter,
|
||||
const char* key, const char* val) {
|
||||
size_t len;
|
||||
const char* str;
|
||||
str = rocksdb_iter_key(iter, &len);
|
||||
@@ -173,9 +185,10 @@ static void CheckIter(rocksdb_iterator_t* iter, const char* key,
|
||||
}
|
||||
|
||||
// Callback from rocksdb_writebatch_iterate()
|
||||
static void CheckPut(void* ptr, const char* k, size_t klen, const char* v,
|
||||
size_t vlen) {
|
||||
int* state = (int*)ptr;
|
||||
static void CheckPut(void* ptr,
|
||||
const char* k, size_t klen,
|
||||
const char* v, size_t vlen) {
|
||||
int* state = (int*) ptr;
|
||||
CheckCondition(*state < 2);
|
||||
switch (*state) {
|
||||
case 0:
|
||||
@@ -192,7 +205,7 @@ static void CheckPut(void* ptr, const char* k, size_t klen, const char* v,
|
||||
|
||||
// Callback from rocksdb_writebatch_iterate()
|
||||
static void CheckDel(void* ptr, const char* k, size_t klen) {
|
||||
int* state = (int*)ptr;
|
||||
int* state = (int*) ptr;
|
||||
CheckCondition(*state == 2);
|
||||
CheckEqual("bar", k, klen);
|
||||
(*state)++;
|
||||
@@ -200,16 +213,14 @@ static void CheckDel(void* ptr, const char* k, size_t klen) {
|
||||
|
||||
static void CmpDestroy(void* arg) { (void)arg; }
|
||||
|
||||
static int CmpCompare(void* arg, const char* a, size_t alen, const char* b,
|
||||
size_t blen) {
|
||||
static int CmpCompare(void* arg, const char* a, size_t alen,
|
||||
const char* b, size_t blen) {
|
||||
(void)arg;
|
||||
size_t n = (alen < blen) ? alen : blen;
|
||||
int r = memcmp(a, b, n);
|
||||
if (r == 0) {
|
||||
if (alen < blen)
|
||||
r = -1;
|
||||
else if (alen > blen)
|
||||
r = +1;
|
||||
if (alen < blen) r = -1;
|
||||
else if (alen > blen) r = +1;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@@ -394,9 +405,11 @@ static const char* MergeOperatorName(void* arg) {
|
||||
return "TestMergeOperator";
|
||||
}
|
||||
static char* MergeOperatorFullMerge(
|
||||
void* arg, const char* key, size_t key_length, const char* existing_value,
|
||||
size_t existing_value_length, const char* const* operands_list,
|
||||
const size_t* operands_list_length, int num_operands,
|
||||
void* arg,
|
||||
const char* key, size_t key_length,
|
||||
const char* existing_value, size_t existing_value_length,
|
||||
const char* const* operands_list, const size_t* operands_list_length,
|
||||
int num_operands,
|
||||
unsigned char* success, size_t* new_value_length) {
|
||||
(void)arg;
|
||||
(void)key;
|
||||
@@ -412,12 +425,12 @@ static char* MergeOperatorFullMerge(
|
||||
memcpy(result, "fake", 4);
|
||||
return result;
|
||||
}
|
||||
static char* MergeOperatorPartialMerge(void* arg, const char* key,
|
||||
size_t key_length,
|
||||
const char* const* operands_list,
|
||||
const size_t* operands_list_length,
|
||||
int num_operands, unsigned char* success,
|
||||
size_t* new_value_length) {
|
||||
static char* MergeOperatorPartialMerge(
|
||||
void* arg,
|
||||
const char* key, size_t key_length,
|
||||
const char* const* operands_list, const size_t* operands_list_length,
|
||||
int num_operands,
|
||||
unsigned char* success, size_t* new_value_length) {
|
||||
(void)arg;
|
||||
(void)key;
|
||||
(void)key_length;
|
||||
@@ -431,16 +444,18 @@ static char* MergeOperatorPartialMerge(void* arg, const char* key,
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CheckTxnGet(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options, const char* key,
|
||||
const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transaction_get(txn, options, key, strlen(key), &val_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
static void CheckTxnGet(
|
||||
rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key,
|
||||
const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transaction_get(txn, options, key, strlen(key), &val_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnGetCF(rocksdb_transaction_t* txn,
|
||||
@@ -487,22 +502,11 @@ static void CheckTxnPinGetCF(rocksdb_transaction_t* txn,
|
||||
rocksdb_pinnableslice_destroy(p);
|
||||
}
|
||||
|
||||
static void CheckTxnGetForUpdate(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transaction_get_for_update(txn, options, key, strlen(key),
|
||||
&val_len, true, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnDBGet(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options, const char* key,
|
||||
const char* expected) {
|
||||
static void CheckTxnDBGet(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key,
|
||||
const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
@@ -527,20 +531,6 @@ static void CheckTxnDBGetCF(rocksdb_transactiondb_t* txn_db,
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnGetForUpdateCF(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family, const char* key,
|
||||
const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transaction_get_for_update_cf(
|
||||
txn, options, column_family, key, strlen(key), &val_len, true, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnDBPinGet(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
const char* key, const char* expected) {
|
||||
@@ -642,7 +632,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_t* db;
|
||||
rocksdb_comparator_t* cmp;
|
||||
rocksdb_cache_t* cache;
|
||||
rocksdb_dbpath_t* dbpath;
|
||||
rocksdb_dbpath_t *dbpath;
|
||||
rocksdb_env_t* env;
|
||||
rocksdb_options_t* options;
|
||||
rocksdb_compactoptions_t* coptions;
|
||||
@@ -659,20 +649,30 @@ int main(int argc, char** argv) {
|
||||
char* err = NULL;
|
||||
int run = -1;
|
||||
|
||||
snprintf(dbname, sizeof(dbname), "%s/rocksdb_c_test-%d", GetTempDir(),
|
||||
((int)geteuid()));
|
||||
snprintf(dbname, sizeof(dbname),
|
||||
"%s/rocksdb_c_test-%d",
|
||||
GetTempDir(),
|
||||
((int) geteuid()));
|
||||
|
||||
snprintf(dbbackupname, sizeof(dbbackupname), "%s/rocksdb_c_test-%d-backup",
|
||||
GetTempDir(), ((int)geteuid()));
|
||||
snprintf(dbbackupname, sizeof(dbbackupname),
|
||||
"%s/rocksdb_c_test-%d-backup",
|
||||
GetTempDir(),
|
||||
((int) geteuid()));
|
||||
|
||||
snprintf(dbcheckpointname, sizeof(dbcheckpointname),
|
||||
"%s/rocksdb_c_test-%d-checkpoint", GetTempDir(), ((int)geteuid()));
|
||||
"%s/rocksdb_c_test-%d-checkpoint",
|
||||
GetTempDir(),
|
||||
((int) geteuid()));
|
||||
|
||||
snprintf(sstfilename, sizeof(sstfilename), "%s/rocksdb_c_test-%d-sst",
|
||||
GetTempDir(), ((int)geteuid()));
|
||||
snprintf(sstfilename, sizeof(sstfilename),
|
||||
"%s/rocksdb_c_test-%d-sst",
|
||||
GetTempDir(),
|
||||
((int)geteuid()));
|
||||
|
||||
snprintf(dbpathname, sizeof(dbpathname), "%s/rocksdb_c_test-%d-dbpath",
|
||||
GetTempDir(), ((int)geteuid()));
|
||||
snprintf(dbpathname, sizeof(dbpathname),
|
||||
"%s/rocksdb_c_test-%d-dbpath",
|
||||
GetTempDir(),
|
||||
((int) geteuid()));
|
||||
|
||||
StartPhase("create_objects");
|
||||
cmp = rocksdb_comparator_create(NULL, CmpDestroy, CmpCompare, CmpName);
|
||||
@@ -720,8 +720,6 @@ int main(int argc, char** argv) {
|
||||
|
||||
rocksdb_options_add_compact_on_deletion_collector_factory(options, 10000,
|
||||
10001);
|
||||
rocksdb_options_add_compact_on_deletion_collector_factory_del_ratio(
|
||||
options, 10000, 10001, 0.0);
|
||||
|
||||
StartPhase("destroy");
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
@@ -748,8 +746,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_destroy_db(options, dbbackupname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_backup_engine_t* be =
|
||||
rocksdb_backup_engine_open(options, dbbackupname, &err);
|
||||
rocksdb_backup_engine_t *be = rocksdb_backup_engine_open(options, dbbackupname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_backup_engine_create_new_backup(be, db, &err);
|
||||
@@ -762,8 +759,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_backup_engine_create_new_backup(be, db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
const rocksdb_backup_engine_info_t* bei =
|
||||
rocksdb_backup_engine_get_backup_info(be);
|
||||
const rocksdb_backup_engine_info_t* bei = rocksdb_backup_engine_get_backup_info(be);
|
||||
CheckCondition(rocksdb_backup_engine_info_count(bei) > 1);
|
||||
rocksdb_backup_engine_info_destroy(bei);
|
||||
|
||||
@@ -782,11 +778,9 @@ int main(int argc, char** argv) {
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_restore_options_t* restore_options =
|
||||
rocksdb_restore_options_create();
|
||||
rocksdb_restore_options_t *restore_options = rocksdb_restore_options_create();
|
||||
rocksdb_restore_options_set_keep_log_files(restore_options, 0);
|
||||
rocksdb_backup_engine_restore_db_from_latest_backup(be, dbname, dbname,
|
||||
restore_options, &err);
|
||||
rocksdb_backup_engine_restore_db_from_latest_backup(be, dbname, dbname, restore_options, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_restore_options_destroy(restore_options);
|
||||
|
||||
@@ -805,8 +799,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_destroy_db(options, dbcheckpointname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_checkpoint_t* checkpoint =
|
||||
rocksdb_checkpoint_object_create(db, &err);
|
||||
rocksdb_checkpoint_t* checkpoint = rocksdb_checkpoint_object_create(db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_checkpoint_create(checkpoint, dbcheckpointname, 0, &err);
|
||||
@@ -983,10 +976,10 @@ int main(int argc, char** argv) {
|
||||
StartPhase("writebatch_vectors");
|
||||
{
|
||||
rocksdb_writebatch_t* wb = rocksdb_writebatch_create();
|
||||
const char* k_list[2] = {"z", "ap"};
|
||||
const size_t k_sizes[2] = {1, 2};
|
||||
const char* v_list[3] = {"x", "y", "z"};
|
||||
const size_t v_sizes[3] = {1, 1, 1};
|
||||
const char* k_list[2] = { "z", "ap" };
|
||||
const size_t k_sizes[2] = { 1, 2 };
|
||||
const char* v_list[3] = { "x", "y", "z" };
|
||||
const size_t v_sizes[3] = { 1, 1, 1 };
|
||||
rocksdb_writebatch_putv(wb, 2, k_list, k_sizes, 3, v_list, v_sizes);
|
||||
rocksdb_write(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
@@ -1048,17 +1041,13 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(count == 3);
|
||||
size_t size;
|
||||
char* value;
|
||||
value = rocksdb_writebatch_wi_get_from_batch(wbi, options, "box", 3, &size,
|
||||
&err);
|
||||
value = rocksdb_writebatch_wi_get_from_batch(wbi, options, "box", 3, &size, &err);
|
||||
CheckValue(err, "c", &value, size);
|
||||
value = rocksdb_writebatch_wi_get_from_batch(wbi, options, "bar", 3, &size,
|
||||
&err);
|
||||
value = rocksdb_writebatch_wi_get_from_batch(wbi, options, "bar", 3, &size, &err);
|
||||
CheckValue(err, NULL, &value, size);
|
||||
value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions,
|
||||
"foo", 3, &size, &err);
|
||||
value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions, "foo", 3, &size, &err);
|
||||
CheckValue(err, "hello", &value, size);
|
||||
value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions,
|
||||
"box", 3, &size, &err);
|
||||
value = rocksdb_writebatch_wi_get_from_batch_and_db(wbi, db, roptions, "box", 3, &size, &err);
|
||||
CheckValue(err, "c", &value, size);
|
||||
rocksdb_write_writebatch_wi(db, woptions, wbi, &err);
|
||||
CheckNoError(err);
|
||||
@@ -1075,10 +1064,10 @@ int main(int argc, char** argv) {
|
||||
StartPhase("writebatch_wi_vectors");
|
||||
{
|
||||
rocksdb_writebatch_wi_t* wb = rocksdb_writebatch_wi_create(0, 1);
|
||||
const char* k_list[2] = {"z", "ap"};
|
||||
const size_t k_sizes[2] = {1, 2};
|
||||
const char* v_list[3] = {"x", "y", "z"};
|
||||
const size_t v_sizes[3] = {1, 1, 1};
|
||||
const char* k_list[2] = { "z", "ap" };
|
||||
const size_t k_sizes[2] = { 1, 2 };
|
||||
const char* v_list[3] = { "x", "y", "z" };
|
||||
const size_t v_sizes[3] = { 1, 1, 1 };
|
||||
rocksdb_writebatch_wi_putv(wb, 2, k_list, k_sizes, 3, v_list, v_sizes);
|
||||
rocksdb_write_writebatch_wi(db, woptions, wb, &err);
|
||||
CheckNoError(err);
|
||||
@@ -1167,14 +1156,13 @@ int main(int argc, char** argv) {
|
||||
|
||||
StartPhase("multiget");
|
||||
{
|
||||
const char* keys[3] = {"box", "foo", "notfound"};
|
||||
const size_t keys_sizes[3] = {3, 3, 8};
|
||||
const char* keys[3] = { "box", "foo", "notfound" };
|
||||
const size_t keys_sizes[3] = { 3, 3, 8 };
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
const char* expected[3] = {"c", "hello", NULL};
|
||||
rocksdb_multi_get(db, roptions, 3, keys, keys_sizes, vals, vals_sizes,
|
||||
errs);
|
||||
rocksdb_multi_get(db, roptions, 3, keys, keys_sizes, vals, vals_sizes, errs);
|
||||
CheckMultiGetValues(3, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
@@ -1192,10 +1180,10 @@ int main(int argc, char** argv) {
|
||||
char keybuf[100];
|
||||
char valbuf[100];
|
||||
uint64_t sizes[2];
|
||||
const char* start[2] = {"a", "k00000000000000010000"};
|
||||
size_t start_len[2] = {1, 21};
|
||||
const char* limit[2] = {"k00000000000000010000", "z"};
|
||||
size_t limit_len[2] = {21, 1};
|
||||
const char* start[2] = { "a", "k00000000000000010000" };
|
||||
size_t start_len[2] = { 1, 21 };
|
||||
const char* limit[2] = { "k00000000000000010000", "z" };
|
||||
size_t limit_len[2] = { 21, 1 };
|
||||
rocksdb_writeoptions_set_sync(woptions, 0);
|
||||
for (i = 0; i < n; i++) {
|
||||
snprintf(keybuf, sizeof(keybuf), "k%020d", i);
|
||||
@@ -1405,8 +1393,8 @@ int main(int argc, char** argv) {
|
||||
factory);
|
||||
db = CheckCompaction(db, options_with_filter_factory, roptions, woptions);
|
||||
|
||||
rocksdb_options_set_compaction_filter_factory(options_with_filter_factory,
|
||||
NULL);
|
||||
rocksdb_options_set_compaction_filter_factory(
|
||||
options_with_filter_factory, NULL);
|
||||
rocksdb_options_destroy(options_with_filter_factory);
|
||||
}
|
||||
|
||||
@@ -1461,8 +1449,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_close(db);
|
||||
|
||||
size_t cflen;
|
||||
char** column_fams =
|
||||
rocksdb_list_column_families(db_options, dbname, &cflen, &err);
|
||||
char** column_fams = rocksdb_list_column_families(db_options, dbname, &cflen, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual("default", column_fams[0], 7);
|
||||
CheckEqual("cf1", column_fams[1], 3);
|
||||
@@ -1478,8 +1465,7 @@ int main(int argc, char** argv) {
|
||||
LoadAndCheckLatestOptions(dbname, env, false, cache, NULL, 2, cf_names,
|
||||
NULL);
|
||||
|
||||
db = rocksdb_open_column_families(db_options, dbname, 2, cf_names, cf_opts,
|
||||
handles, &err);
|
||||
db = rocksdb_open_column_families(db_options, dbname, 2, cf_names, cf_opts, handles, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_put_cf(db, woptions, handles[1], "foo", 3, "hello", 5, &err);
|
||||
@@ -1497,10 +1483,11 @@ int main(int argc, char** argv) {
|
||||
&err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_flushoptions_t* flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_t *flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
rocksdb_flush_cf(db, flush_options, handles[1], &err);
|
||||
CheckNoError(err) rocksdb_flushoptions_destroy(flush_options);
|
||||
CheckNoError(err)
|
||||
rocksdb_flushoptions_destroy(flush_options);
|
||||
|
||||
CheckGetCF(db, roptions, handles[1], "foo", "hello");
|
||||
CheckPinGetCF(db, roptions, handles[1], "foo", "hello");
|
||||
@@ -1537,29 +1524,27 @@ int main(int argc, char** argv) {
|
||||
rocksdb_flush_wal(db, 1, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
const char* keys[3] = {"box", "box", "barfooxx"};
|
||||
const rocksdb_column_family_handle_t* get_handles[3] = {
|
||||
handles[0], handles[1], handles[1]};
|
||||
const size_t keys_sizes[3] = {3, 3, 8};
|
||||
const char* keys[3] = { "box", "box", "barfooxx" };
|
||||
const rocksdb_column_family_handle_t* get_handles[3] = { handles[0], handles[1], handles[1] };
|
||||
const size_t keys_sizes[3] = { 3, 3, 8 };
|
||||
char* vals[3];
|
||||
size_t vals_sizes[3];
|
||||
char* errs[3];
|
||||
rocksdb_multi_get_cf(db, roptions, get_handles, 3, keys, keys_sizes, vals,
|
||||
vals_sizes, errs);
|
||||
rocksdb_multi_get_cf(db, roptions, get_handles, 3, keys, keys_sizes, vals, vals_sizes, errs);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 3; i++) {
|
||||
CheckEqual(NULL, errs[i], 0);
|
||||
switch (i) {
|
||||
case 0:
|
||||
CheckEqual(NULL, vals[i], vals_sizes[i]); // wrong cf
|
||||
break;
|
||||
case 1:
|
||||
CheckEqual("c", vals[i], vals_sizes[i]); // bingo
|
||||
break;
|
||||
case 2:
|
||||
CheckEqual(NULL, vals[i], vals_sizes[i]); // normal not found
|
||||
break;
|
||||
case 0:
|
||||
CheckEqual(NULL, vals[i], vals_sizes[i]); // wrong cf
|
||||
break;
|
||||
case 1:
|
||||
CheckEqual("c", vals[i], vals_sizes[i]); // bingo
|
||||
break;
|
||||
case 2:
|
||||
CheckEqual(NULL, vals[i], vals_sizes[i]); // normal not found
|
||||
break;
|
||||
}
|
||||
Free(&vals[i]);
|
||||
}
|
||||
@@ -1607,8 +1592,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_iterator_t* iter =
|
||||
rocksdb_create_iterator_cf(db, roptions, handles[1]);
|
||||
rocksdb_iterator_t* iter = rocksdb_create_iterator_cf(db, roptions, handles[1]);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
@@ -1621,11 +1605,9 @@ int main(int argc, char** argv) {
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
rocksdb_column_family_handle_t* iters_cf_handles[2] = {handles[0],
|
||||
handles[1]};
|
||||
rocksdb_column_family_handle_t* iters_cf_handles[2] = { handles[0], handles[1] };
|
||||
rocksdb_iterator_t* iters_handles[2];
|
||||
rocksdb_create_iterators(db, roptions, iters_cf_handles, iters_handles, 2,
|
||||
&err);
|
||||
rocksdb_create_iterators(db, roptions, iters_cf_handles, iters_handles, 2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
iter = iters_handles[0];
|
||||
@@ -1670,11 +1652,9 @@ int main(int argc, char** argv) {
|
||||
{
|
||||
// Create new database
|
||||
rocksdb_options_set_allow_mmap_reads(options, 1);
|
||||
rocksdb_options_set_prefix_extractor(
|
||||
options, rocksdb_slicetransform_create_fixed_prefix(3));
|
||||
rocksdb_options_set_prefix_extractor(options, rocksdb_slicetransform_create_fixed_prefix(3));
|
||||
rocksdb_options_set_hash_skip_list_rep(options, 5000, 4, 4);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16, 0, 0, 0,
|
||||
0);
|
||||
rocksdb_options_set_plain_table_factory(options, 4, 10, 0.75, 16);
|
||||
rocksdb_options_set_allow_concurrent_memtable_write(options, 0);
|
||||
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
@@ -1767,9 +1747,8 @@ int main(int argc, char** argv) {
|
||||
// amount of memory used within memtables should grow
|
||||
CheckCondition(rocksdb_approximate_memory_usage_get_mem_table_total(mu2) >=
|
||||
rocksdb_approximate_memory_usage_get_mem_table_total(mu1));
|
||||
CheckCondition(
|
||||
rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu2) >=
|
||||
rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu1));
|
||||
CheckCondition(rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu2) >=
|
||||
rocksdb_approximate_memory_usage_get_mem_table_unflushed(mu1));
|
||||
|
||||
rocksdb_memory_consumers_destroy(consumers);
|
||||
rocksdb_approximate_memory_usage_destroy(mu1);
|
||||
@@ -2060,15 +2039,6 @@ int main(int argc, char** argv) {
|
||||
CheckCondition(29.0 ==
|
||||
rocksdb_options_get_experimental_mempurge_threshold(o));
|
||||
|
||||
CheckCondition(rocksdb_statistics_level_disable_all ==
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
rocksdb_options_enable_statistics(o);
|
||||
CheckCondition(rocksdb_statistics_level_disable_all !=
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
rocksdb_options_set_statistics_level(o, rocksdb_statistics_level_all);
|
||||
CheckCondition(rocksdb_statistics_level_all ==
|
||||
rocksdb_options_get_statistics_level(o));
|
||||
|
||||
/* Blob Options */
|
||||
rocksdb_options_set_enable_blob_files(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_files(o));
|
||||
@@ -2608,9 +2578,6 @@ int main(int argc, char** argv) {
|
||||
rocksdb_readoptions_set_io_timeout(ro, 400);
|
||||
CheckCondition(400 == rocksdb_readoptions_get_io_timeout(ro));
|
||||
|
||||
rocksdb_readoptions_set_async_io(ro, 1);
|
||||
CheckCondition(1 == rocksdb_readoptions_get_async_io(ro));
|
||||
|
||||
rocksdb_readoptions_destroy(ro);
|
||||
}
|
||||
|
||||
@@ -2872,57 +2839,53 @@ int main(int argc, char** argv) {
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_put(db, woptions, "a", 1, "0", 1, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo", 3, "bar", 3, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo1", 4, "bar1", 4, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "g1", 2, "0", 1, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "a", 1, "0", 1, &err); CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo", 3, "bar", 3, &err); CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "foo1", 4, "bar1", 4, &err); CheckNoError(err);
|
||||
rocksdb_put(db, woptions, "g1", 2, "0", 1, &err); CheckNoError(err);
|
||||
|
||||
// testing basic case with no iterate_upper_bound and no prefix_extractor
|
||||
{
|
||||
rocksdb_readoptions_set_iterate_upper_bound(roptions, NULL, 0);
|
||||
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
|
||||
rocksdb_readoptions_set_iterate_upper_bound(roptions, NULL, 0);
|
||||
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
|
||||
|
||||
rocksdb_iter_seek(iter, "foo", 3);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "foo", "bar");
|
||||
rocksdb_iter_seek(iter, "foo", 3);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "foo", "bar");
|
||||
|
||||
rocksdb_iter_next(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "foo1", "bar1");
|
||||
rocksdb_iter_next(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "foo1", "bar1");
|
||||
|
||||
rocksdb_iter_next(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "g1", "0");
|
||||
rocksdb_iter_next(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "g1", "0");
|
||||
|
||||
rocksdb_iter_destroy(iter);
|
||||
rocksdb_iter_destroy(iter);
|
||||
}
|
||||
|
||||
// testing iterate_upper_bound and forward iterator
|
||||
// to make sure it stops at bound
|
||||
{
|
||||
// iterate_upper_bound points beyond the last expected entry
|
||||
rocksdb_readoptions_set_iterate_upper_bound(roptions, "foo2", 4);
|
||||
// iterate_upper_bound points beyond the last expected entry
|
||||
rocksdb_readoptions_set_iterate_upper_bound(roptions, "foo2", 4);
|
||||
|
||||
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
|
||||
rocksdb_iterator_t* iter = rocksdb_create_iterator(db, roptions);
|
||||
|
||||
rocksdb_iter_seek(iter, "foo", 3);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "foo", "bar");
|
||||
rocksdb_iter_seek(iter, "foo", 3);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "foo", "bar");
|
||||
|
||||
rocksdb_iter_next(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "foo1", "bar1");
|
||||
rocksdb_iter_next(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "foo1", "bar1");
|
||||
|
||||
rocksdb_iter_next(iter);
|
||||
// should stop here...
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_next(iter);
|
||||
// should stop here...
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
|
||||
rocksdb_iter_destroy(iter);
|
||||
rocksdb_readoptions_set_iterate_upper_bound(roptions, NULL, 0);
|
||||
rocksdb_iter_destroy(iter);
|
||||
rocksdb_readoptions_set_iterate_upper_bound(roptions, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3046,7 +3009,7 @@ int main(int argc, char** argv) {
|
||||
snapshot = rocksdb_transactiondb_create_snapshot(txn_db);
|
||||
rocksdb_readoptions_set_snapshot(roptions, snapshot);
|
||||
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hey", 3, &err);
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hey", 3, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
CheckTxnDBGet(txn_db, roptions, "foo", "hello");
|
||||
@@ -3058,8 +3021,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
// iterate
|
||||
rocksdb_transaction_put(txn, "bar", 3, "hi", 2, &err);
|
||||
rocksdb_iterator_t* iter =
|
||||
rocksdb_transaction_create_iterator(txn, roptions);
|
||||
rocksdb_iterator_t* iter = rocksdb_transaction_create_iterator(txn, roptions);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
@@ -3130,17 +3092,6 @@ int main(int argc, char** argv) {
|
||||
CheckTxnDBGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
CheckTxnDBPinGetCF(txn_db, roptions, cfh, "cf_foo", NULL);
|
||||
|
||||
// memory usage
|
||||
rocksdb_t* base_db = rocksdb_transactiondb_get_base_db(txn_db);
|
||||
rocksdb_memory_consumers_t* consumers = rocksdb_memory_consumers_create();
|
||||
rocksdb_memory_consumers_add_db(consumers, base_db);
|
||||
rocksdb_memory_usage_t* usage =
|
||||
rocksdb_approximate_memory_usage_create(consumers, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_approximate_memory_usage_destroy(usage);
|
||||
rocksdb_memory_consumers_destroy(consumers);
|
||||
rocksdb_transactiondb_close_base_db(base_db);
|
||||
|
||||
// flush
|
||||
rocksdb_flushoptions_t* flush_options = rocksdb_flushoptions_create();
|
||||
rocksdb_flushoptions_set_wait(flush_options, 1);
|
||||
@@ -3253,120 +3204,6 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transactiondb_options_destroy(txn_db_options);
|
||||
}
|
||||
|
||||
StartPhase("transactions_multi_get_for_update");
|
||||
{
|
||||
// open a TransactionDB
|
||||
txn_db_options = rocksdb_transactiondb_options_create();
|
||||
rocksdb_transactiondb_options_set_transaction_lock_timeout(txn_db_options,
|
||||
0);
|
||||
txn_options = rocksdb_transaction_options_create();
|
||||
rocksdb_options_set_create_if_missing(options, 1);
|
||||
txn_db = rocksdb_transactiondb_open(options, txn_db_options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "foo", 3, "hey", 3, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transactiondb_put(txn_db, woptions, "bar", 3, "hello", 5, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// begin transactions
|
||||
txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
rocksdb_transaction_t* txn2 =
|
||||
rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
|
||||
// multi get
|
||||
{
|
||||
const char* keys[2] = {"foo", "bar"};
|
||||
const size_t keys_sizes[2] = {3, 3};
|
||||
char* vals[2];
|
||||
size_t vals_sizes[2];
|
||||
char* errs[2];
|
||||
const char* expected[2] = {"hey", "hello"};
|
||||
rocksdb_transaction_multi_get_for_update(
|
||||
txn, roptions, 2, keys, keys_sizes, vals, vals_sizes, errs);
|
||||
CheckMultiGetValues(2, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
char* conflict_err = NULL;
|
||||
size_t val_len;
|
||||
rocksdb_transaction_get_for_update(txn2, roptions, "foo", 3, &val_len, true,
|
||||
&conflict_err);
|
||||
// get-for-update conflict
|
||||
CheckCondition(conflict_err != NULL);
|
||||
Free(&conflict_err);
|
||||
|
||||
// commit
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// should work after first tx is commited
|
||||
CheckTxnGetForUpdate(txn2, roptions, "foo", "hey");
|
||||
|
||||
// commit the second one
|
||||
rocksdb_transaction_commit(txn2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// destroy txns
|
||||
rocksdb_transaction_destroy(txn);
|
||||
rocksdb_transaction_destroy(txn2);
|
||||
|
||||
// same for column families
|
||||
|
||||
rocksdb_column_family_handle_t* cfh;
|
||||
cfh = rocksdb_transactiondb_create_column_family(txn_db, options,
|
||||
"txn_db_cf", &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_transactiondb_put_cf(txn_db, woptions, cfh, "cf_foo", 6, "cf_hello",
|
||||
8, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transactiondb_put_cf(txn_db, woptions, cfh, "cf_bar", 6, "cf_hey",
|
||||
6, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
txn = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
txn2 = rocksdb_transaction_begin(txn_db, woptions, txn_options, NULL);
|
||||
|
||||
{
|
||||
const rocksdb_column_family_handle_t* get_handles[2] = {cfh, cfh};
|
||||
const char* keys[2] = {"cf_foo", "cf_bar"};
|
||||
const size_t keys_sizes[2] = {6, 6};
|
||||
char* vals[2];
|
||||
size_t vals_sizes[2];
|
||||
char* errs[2];
|
||||
const char* expected[2] = {"cf_hello", "cf_hey"};
|
||||
rocksdb_transaction_multi_get_for_update_cf(txn, roptions, get_handles, 2,
|
||||
keys, keys_sizes, vals,
|
||||
vals_sizes, errs);
|
||||
CheckMultiGetValues(2, vals, vals_sizes, errs, expected);
|
||||
}
|
||||
|
||||
char* conflict_err_cf = NULL;
|
||||
size_t val_len_cf;
|
||||
rocksdb_transaction_get_for_update_cf(txn2, roptions, cfh, "cf_foo", 6,
|
||||
&val_len_cf, true, &conflict_err_cf);
|
||||
CheckCondition(conflict_err_cf != NULL);
|
||||
Free(&conflict_err_cf);
|
||||
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
CheckTxnGetForUpdateCF(txn2, roptions, cfh, "cf_foo", "cf_hello");
|
||||
|
||||
rocksdb_transaction_commit(txn2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// close and destroy
|
||||
rocksdb_column_family_handle_destroy(cfh);
|
||||
rocksdb_transaction_destroy(txn);
|
||||
rocksdb_transaction_destroy(txn2);
|
||||
rocksdb_transactiondb_close(txn_db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_options_destroy(txn_options);
|
||||
rocksdb_transactiondb_options_destroy(txn_db_options);
|
||||
}
|
||||
|
||||
StartPhase("optimistic_transactions");
|
||||
{
|
||||
rocksdb_options_t* db_options = rocksdb_options_create();
|
||||
@@ -3396,19 +3233,8 @@ int main(int argc, char** argv) {
|
||||
rocksdb_put(db, woptions, "key", 3, "value", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_column_family_handle_t *cfh1, *cfh2;
|
||||
char** list_const_cf_names = (char**)malloc(2 * sizeof(char*));
|
||||
list_const_cf_names[0] = "txn_db_cf1";
|
||||
list_const_cf_names[1] = "txn_db_cf2";
|
||||
size_t cflen;
|
||||
rocksdb_column_family_handle_t** list_cfh = rocksdb_create_column_families(
|
||||
db, db_options, 2, (const char* const*)list_const_cf_names, &cflen,
|
||||
&err);
|
||||
free(list_const_cf_names);
|
||||
CheckNoError(err);
|
||||
assert(cflen == 2);
|
||||
cfh1 = list_cfh[0];
|
||||
cfh2 = list_cfh[1];
|
||||
rocksdb_create_column_families_destroy(list_cfh);
|
||||
cfh1 = rocksdb_create_column_family(db, db_options, "txn_db_cf1", &err);
|
||||
cfh2 = rocksdb_create_column_family(db, db_options, "txn_db_cf2", &err);
|
||||
txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options,
|
||||
NULL);
|
||||
rocksdb_transaction_put_cf(txn, cfh1, "key_cf1", 7, "val_cf1", 7, &err);
|
||||
@@ -3622,71 +3448,6 @@ int main(int argc, char** argv) {
|
||||
rocksdb_readoptions_destroy(ropts);
|
||||
}
|
||||
|
||||
StartPhase("statistics");
|
||||
{
|
||||
const uint32_t BYTES_WRITTEN_TICKER = 40;
|
||||
const uint32_t DB_WRITE_HIST = 1;
|
||||
|
||||
rocksdb_statistics_histogram_data_t* hist =
|
||||
rocksdb_statistics_histogram_data_create();
|
||||
{
|
||||
// zero by default
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 ==
|
||||
rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 ==
|
||||
rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_min(hist));
|
||||
}
|
||||
|
||||
rocksdb_close(db);
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_options_enable_statistics(options);
|
||||
rocksdb_options_set_statistics_level(options, rocksdb_statistics_level_all);
|
||||
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
CheckCondition(0 == rocksdb_options_statistics_get_ticker_count(
|
||||
options, BYTES_WRITTEN_TICKER));
|
||||
rocksdb_options_statistics_get_histogram_data(options, DB_WRITE_HIST, hist);
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 == rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 == rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 10; ++i) {
|
||||
char key = '0' + (char)i;
|
||||
rocksdb_put(db, woptions, &key, 1, "", 1, &err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
CheckCondition(0 != rocksdb_options_statistics_get_ticker_count(
|
||||
options, BYTES_WRITTEN_TICKER));
|
||||
rocksdb_options_statistics_get_histogram_data(options, DB_WRITE_HIST, hist);
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_median(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_p95(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_p99(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_average(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_std_dev(hist));
|
||||
CheckCondition(0.0 != rocksdb_statistics_histogram_data_get_max(hist));
|
||||
CheckCondition(0 != rocksdb_statistics_histogram_data_get_count(hist));
|
||||
CheckCondition(0 != rocksdb_statistics_histogram_data_get_sum(hist));
|
||||
|
||||
rocksdb_statistics_histogram_data_destroy(hist);
|
||||
}
|
||||
|
||||
StartPhase("cancel_all_background_work");
|
||||
rocksdb_cancel_all_background_work(db, 1);
|
||||
|
||||
@@ -3705,3 +3466,12 @@ int main(int argc, char** argv) {
|
||||
fprintf(stderr, "PASS\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int main(void) {
|
||||
fprintf(stderr, "SKIPPED\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
+88
-165
@@ -53,9 +53,11 @@ ColumnFamilyHandleImpl::ColumnFamilyHandleImpl(
|
||||
|
||||
ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
|
||||
if (cfd_ != nullptr) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
for (auto& listener : cfd_->ioptions()->listeners) {
|
||||
listener->OnColumnFamilyHandleDeletionStarted(this);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
// Job id == 0 means that this is not our background process, but rather
|
||||
// user thread
|
||||
// Need to hold some shared pointers owned by the initial_cf_options
|
||||
@@ -86,10 +88,15 @@ const std::string& ColumnFamilyHandleImpl::GetName() const {
|
||||
}
|
||||
|
||||
Status ColumnFamilyHandleImpl::GetDescriptor(ColumnFamilyDescriptor* desc) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// accessing mutable cf-options requires db mutex.
|
||||
InstrumentedMutexLock l(mutex_);
|
||||
*desc = ColumnFamilyDescriptor(cfd()->GetName(), cfd()->GetLatestCFOptions());
|
||||
return Status::OK();
|
||||
#else
|
||||
(void)desc;
|
||||
return Status::NotSupported();
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
const Comparator* ColumnFamilyHandleImpl::GetComparator() const {
|
||||
@@ -185,7 +192,8 @@ Status CheckCFPathsSupported(const DBOptions& db_options,
|
||||
return Status::NotSupported(
|
||||
"More than one CF paths are only supported in "
|
||||
"universal and level compaction styles. ");
|
||||
} else if (cf_options.cf_paths.empty() && db_options.db_paths.size() > 1) {
|
||||
} else if (cf_options.cf_paths.empty() &&
|
||||
db_options.db_paths.size() > 1) {
|
||||
return Status::NotSupported(
|
||||
"More than one DB paths are only supported in "
|
||||
"universal and level compaction styles. ");
|
||||
@@ -197,7 +205,7 @@ Status CheckCFPathsSupported(const DBOptions& db_options,
|
||||
namespace {
|
||||
const uint64_t kDefaultTtl = 0xfffffffffffffffe;
|
||||
const uint64_t kDefaultPeriodicCompSecs = 0xfffffffffffffffe;
|
||||
} // anonymous namespace
|
||||
} // namespace
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const ColumnFamilyOptions& src) {
|
||||
@@ -340,17 +348,18 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
result.hard_pending_compaction_bytes_limit;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// When the DB is stopped, it's possible that there are some .trash files that
|
||||
// were not deleted yet, when we open the DB we will find these .trash files
|
||||
// and schedule them to be deleted (or delete immediately if SstFileManager
|
||||
// was not used)
|
||||
auto sfm =
|
||||
static_cast<SstFileManagerImpl*>(db_options.sst_file_manager.get());
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(db_options.sst_file_manager.get());
|
||||
for (size_t i = 0; i < result.cf_paths.size(); i++) {
|
||||
DeleteScheduler::CleanupDirectory(db_options.env, sfm,
|
||||
result.cf_paths[i].path)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (result.cf_paths.empty()) {
|
||||
result.cf_paths = db_options.db_paths;
|
||||
@@ -382,9 +391,8 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
|
||||
const uint64_t kAdjustedTtl = 30 * 24 * 60 * 60;
|
||||
if (result.ttl == kDefaultTtl) {
|
||||
if (is_block_based_table) {
|
||||
// FIFO also requires max_open_files=-1, which is checked in
|
||||
// ValidateOptions().
|
||||
if (is_block_based_table &&
|
||||
result.compaction_style != kCompactionStyleFIFO) {
|
||||
result.ttl = kAdjustedTtl;
|
||||
} else {
|
||||
result.ttl = 0;
|
||||
@@ -392,35 +400,40 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
}
|
||||
|
||||
const uint64_t kAdjustedPeriodicCompSecs = 30 * 24 * 60 * 60;
|
||||
if (result.compaction_style == kCompactionStyleLevel) {
|
||||
|
||||
// Turn on periodic compactions and set them to occur once every 30 days if
|
||||
// compaction filters are used and periodic_compaction_seconds is set to the
|
||||
// default value.
|
||||
if (result.compaction_style != kCompactionStyleFIFO) {
|
||||
if ((result.compaction_filter != nullptr ||
|
||||
result.compaction_filter_factory != nullptr) &&
|
||||
result.periodic_compaction_seconds == kDefaultPeriodicCompSecs &&
|
||||
is_block_based_table) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
} else if (result.compaction_style == kCompactionStyleUniversal) {
|
||||
if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs &&
|
||||
is_block_based_table) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
} else if (result.compaction_style == kCompactionStyleFIFO) {
|
||||
if (result.periodic_compaction_seconds != kDefaultPeriodicCompSecs) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options.info_log.get(),
|
||||
"periodic_compaction_seconds does not support FIFO compaction. You"
|
||||
"may want to set option TTL instead.");
|
||||
} else {
|
||||
// result.compaction_style == kCompactionStyleFIFO
|
||||
if (result.ttl == 0) {
|
||||
if (is_block_based_table) {
|
||||
if (result.periodic_compaction_seconds == kDefaultPeriodicCompSecs) {
|
||||
result.periodic_compaction_seconds = kAdjustedPeriodicCompSecs;
|
||||
}
|
||||
result.ttl = result.periodic_compaction_seconds;
|
||||
}
|
||||
} else if (result.periodic_compaction_seconds != 0) {
|
||||
result.ttl = std::min(result.ttl, result.periodic_compaction_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
// For universal compaction, `ttl` and `periodic_compaction_seconds` mean the
|
||||
// same thing, take the stricter value.
|
||||
if (result.compaction_style == kCompactionStyleUniversal) {
|
||||
if (result.periodic_compaction_seconds == 0) {
|
||||
result.periodic_compaction_seconds = result.ttl;
|
||||
} else if (result.ttl != 0) {
|
||||
// TTL compactions would work similar to Periodic Compactions in Universal in
|
||||
// most of the cases. So, if ttl is set, execute the periodic compaction
|
||||
// codepath.
|
||||
if (result.compaction_style == kCompactionStyleUniversal && result.ttl != 0) {
|
||||
if (result.periodic_compaction_seconds != 0) {
|
||||
result.periodic_compaction_seconds =
|
||||
std::min(result.ttl, result.periodic_compaction_seconds);
|
||||
} else {
|
||||
result.periodic_compaction_seconds = result.ttl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,6 +557,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
next_(nullptr),
|
||||
prev_(nullptr),
|
||||
log_number_(0),
|
||||
flush_reason_(FlushReason::kOthers),
|
||||
column_family_set_(column_family_set),
|
||||
queued_for_flush_(false),
|
||||
queued_for_compaction_(false),
|
||||
@@ -551,8 +565,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
allow_2pc_(db_options.allow_2pc),
|
||||
last_memtable_id_(0),
|
||||
db_paths_registered_(false),
|
||||
mempurge_used_(false),
|
||||
next_epoch_number_(1) {
|
||||
mempurge_used_(false) {
|
||||
if (id_ != kDummyColumnFamilyDataId) {
|
||||
// TODO(cc): RegisterDbPaths can be expensive, considering moving it
|
||||
// outside of this constructor which might be called with db mutex held.
|
||||
@@ -589,6 +602,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
if (ioptions_.compaction_style == kCompactionStyleLevel) {
|
||||
compaction_picker_.reset(
|
||||
new LevelCompactionPicker(ioptions_, &internal_comparator_));
|
||||
#ifndef ROCKSDB_LITE
|
||||
} else if (ioptions_.compaction_style == kCompactionStyleUniversal) {
|
||||
compaction_picker_.reset(
|
||||
new UniversalCompactionPicker(ioptions_, &internal_comparator_));
|
||||
@@ -596,12 +610,13 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
compaction_picker_.reset(
|
||||
new FIFOCompactionPicker(ioptions_, &internal_comparator_));
|
||||
} else if (ioptions_.compaction_style == kCompactionStyleNone) {
|
||||
compaction_picker_.reset(
|
||||
new NullCompactionPicker(ioptions_, &internal_comparator_));
|
||||
compaction_picker_.reset(new NullCompactionPicker(
|
||||
ioptions_, &internal_comparator_));
|
||||
ROCKS_LOG_WARN(ioptions_.logger,
|
||||
"Column family %s does not use any background compaction. "
|
||||
"Compactions can only be done via CompactFiles\n",
|
||||
GetName().c_str());
|
||||
#endif // !ROCKSDB_LITE
|
||||
} else {
|
||||
ROCKS_LOG_ERROR(ioptions_.logger,
|
||||
"Unable to recognize the specified compaction style %d. "
|
||||
@@ -863,9 +878,9 @@ int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger,
|
||||
return static_cast<int>(res);
|
||||
}
|
||||
}
|
||||
} // anonymous namespace
|
||||
} // namespace
|
||||
|
||||
std::pair<WriteStallCondition, WriteStallCause>
|
||||
std::pair<WriteStallCondition, ColumnFamilyData::WriteStallCause>
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
@@ -904,7 +919,7 @@ ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
}
|
||||
|
||||
WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
auto write_stall_condition = WriteStallCondition::kNormal;
|
||||
if (current_ != nullptr) {
|
||||
auto* vstorage = current_->storage_info();
|
||||
@@ -938,8 +953,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::L0_FILE_COUNT_LIMIT_STOPS_WITH_ONGOING_COMPACTION,
|
||||
1);
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
}
|
||||
ROCKS_LOG_WARN(ioptions_.logger,
|
||||
"[%s] Stopping writes because we have %d level-0 files",
|
||||
@@ -960,7 +974,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_DELAYS, 1);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_SLOWDOWNS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.logger,
|
||||
"[%s] Stalling writes because we have %d immutable memtables "
|
||||
@@ -978,11 +992,11 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped || near_stop,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_DELAYS, 1);
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_SLOWDOWNS,
|
||||
1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::L0_FILE_COUNT_LIMIT_DELAYS_WITH_ONGOING_COMPACTION,
|
||||
1);
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, 1);
|
||||
}
|
||||
ROCKS_LOG_WARN(ioptions_.logger,
|
||||
"[%s] Stalling writes because we have %d level-0 files "
|
||||
@@ -998,8 +1012,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
|
||||
(compaction_needed_bytes -
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) >
|
||||
3 *
|
||||
(mutable_cf_options.hard_pending_compaction_bytes_limit -
|
||||
3 * (mutable_cf_options.hard_pending_compaction_bytes_limit -
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) /
|
||||
4;
|
||||
|
||||
@@ -1008,7 +1021,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
prev_compaction_needed_bytes_, was_stopped || near_stop,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_DELAYS, 1);
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.logger,
|
||||
"[%s] Stalling writes because of estimated pending compaction "
|
||||
@@ -1114,9 +1127,12 @@ bool ColumnFamilyData::NeedsCompaction() const {
|
||||
Compaction* ColumnFamilyData::PickCompaction(
|
||||
const MutableCFOptions& mutable_options,
|
||||
const MutableDBOptions& mutable_db_options, LogBuffer* log_buffer) {
|
||||
SequenceNumber earliest_mem_seqno =
|
||||
std::min(mem_->GetEarliestSequenceNumber(),
|
||||
imm_.current()->GetEarliestSequenceNumber(false));
|
||||
auto* result = compaction_picker_->PickCompaction(
|
||||
GetName(), mutable_options, mutable_db_options, current_->storage_info(),
|
||||
log_buffer);
|
||||
log_buffer, earliest_mem_seqno);
|
||||
if (result != nullptr) {
|
||||
result->SetInputVersion(current_);
|
||||
}
|
||||
@@ -1137,7 +1153,6 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
*overlap = false;
|
||||
// Create an InternalIterator over all unflushed memtables
|
||||
Arena arena;
|
||||
// TODO: plumb Env::IOActivity
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena);
|
||||
@@ -1204,7 +1219,6 @@ Compaction* ColumnFamilyData::CompactRange(
|
||||
if (result != nullptr) {
|
||||
result->SetInputVersion(current_);
|
||||
}
|
||||
TEST_SYNC_POINT("ColumnFamilyData::CompactRange:Return");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1241,11 +1255,30 @@ SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(DBImpl* db) {
|
||||
// (if no Scrape happens).
|
||||
assert(ptr != SuperVersion::kSVInUse);
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv == SuperVersion::kSVObsolete) {
|
||||
if (sv == SuperVersion::kSVObsolete ||
|
||||
sv->version_number != super_version_number_.load()) {
|
||||
RecordTick(ioptions_.stats, NUMBER_SUPERVERSION_ACQUIRES);
|
||||
db->mutex()->Lock();
|
||||
SuperVersion* sv_to_delete = nullptr;
|
||||
|
||||
if (sv && sv->Unref()) {
|
||||
RecordTick(ioptions_.stats, NUMBER_SUPERVERSION_CLEANUPS);
|
||||
db->mutex()->Lock();
|
||||
// NOTE: underlying resources held by superversion (sst files) might
|
||||
// not be released until the next background job.
|
||||
sv->Cleanup();
|
||||
if (db->immutable_db_options().avoid_unnecessary_blocking_io) {
|
||||
db->AddSuperVersionsToFreeQueue(sv);
|
||||
db->SchedulePurge();
|
||||
} else {
|
||||
sv_to_delete = sv;
|
||||
}
|
||||
} else {
|
||||
db->mutex()->Lock();
|
||||
}
|
||||
sv = super_version_->Ref();
|
||||
db->mutex()->Unlock();
|
||||
|
||||
delete sv_to_delete;
|
||||
}
|
||||
assert(sv != nullptr);
|
||||
return sv;
|
||||
@@ -1269,8 +1302,8 @@ bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ColumnFamilyData::InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex) {
|
||||
void ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex) {
|
||||
db_mutex->AssertHeld();
|
||||
return InstallSuperVersion(sv_context, mutable_cf_options_);
|
||||
}
|
||||
@@ -1376,33 +1409,6 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
}
|
||||
}
|
||||
|
||||
const auto* ucmp = cf_options.comparator;
|
||||
assert(ucmp);
|
||||
if (ucmp->timestamp_size() > 0 &&
|
||||
!cf_options.persist_user_defined_timestamps) {
|
||||
if (db_options.atomic_flush) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps feature is not supported"
|
||||
"in combination with atomic flush.");
|
||||
}
|
||||
if (db_options.allow_concurrent_memtable_write) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps feature is not supported"
|
||||
" in combination with concurrent memtable write.");
|
||||
}
|
||||
const char* comparator_name = cf_options.comparator->Name();
|
||||
size_t name_size = strlen(comparator_name);
|
||||
const char* suffix = ".u64ts";
|
||||
size_t suffix_size = strlen(suffix);
|
||||
if (name_size <= suffix_size ||
|
||||
strcmp(comparator_name + name_size - suffix_size, suffix) != 0) {
|
||||
return Status::NotSupported(
|
||||
"Not persisting user-defined timestamps"
|
||||
"feature only support user-defined timestamps formatted as "
|
||||
"uint64_t.");
|
||||
}
|
||||
}
|
||||
|
||||
if (cf_options.enable_blob_garbage_collection) {
|
||||
if (cf_options.blob_garbage_collection_age_cutoff < 0.0 ||
|
||||
cf_options.blob_garbage_collection_age_cutoff > 1.0) {
|
||||
@@ -1432,40 +1438,10 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
"Memtable per key-value checksum protection only supports 0, 1, 2, 4 "
|
||||
"or 8 bytes per key.");
|
||||
}
|
||||
if (std::find(supported.begin(), supported.end(),
|
||||
cf_options.block_protection_bytes_per_key) == supported.end()) {
|
||||
return Status::NotSupported(
|
||||
"Block per key-value checksum protection only supports 0, 1, 2, 4 "
|
||||
"or 8 bytes per key.");
|
||||
}
|
||||
|
||||
if (!cf_options.compaction_options_fifo.file_temperature_age_thresholds
|
||||
.empty()) {
|
||||
if (cf_options.compaction_style != kCompactionStyleFIFO) {
|
||||
return Status::NotSupported(
|
||||
"Option file_temperature_age_thresholds only supports FIFO "
|
||||
"compaction.");
|
||||
} else if (cf_options.num_levels > 1) {
|
||||
return Status::NotSupported(
|
||||
"Option file_temperature_age_thresholds is only supported when "
|
||||
"num_levels = 1.");
|
||||
} else {
|
||||
const auto& ages =
|
||||
cf_options.compaction_options_fifo.file_temperature_age_thresholds;
|
||||
assert(ages.size() >= 1);
|
||||
// check that age is sorted
|
||||
for (size_t i = 0; i < ages.size() - 1; ++i) {
|
||||
if (ages[i].age >= ages[i + 1].age) {
|
||||
return Status::NotSupported(
|
||||
"Option file_temperature_age_thresholds requires elements to be "
|
||||
"sorted in increasing order with respect to `age` field.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status ColumnFamilyData::SetOptions(
|
||||
const DBOptions& db_opts,
|
||||
const std::unordered_map<std::string, std::string>& options_map) {
|
||||
@@ -1484,6 +1460,7 @@ Status ColumnFamilyData::SetOptions(
|
||||
}
|
||||
return s;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// REQUIRES: DB mutex held
|
||||
Env::WriteLifeTimeHint ColumnFamilyData::CalculateSSTWriteHint(int level) {
|
||||
@@ -1503,8 +1480,8 @@ Env::WriteLifeTimeHint ColumnFamilyData::CalculateSSTWriteHint(int level) {
|
||||
// than base_level.
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
return static_cast<Env::WriteLifeTimeHint>(
|
||||
level - base_level + static_cast<int>(Env::WLTH_MEDIUM));
|
||||
return static_cast<Env::WriteLifeTimeHint>(level - base_level +
|
||||
static_cast<int>(Env::WLTH_MEDIUM));
|
||||
}
|
||||
|
||||
Status ColumnFamilyData::AddDirectories(
|
||||
@@ -1542,50 +1519,6 @@ FSDirectory* ColumnFamilyData::GetDataDir(size_t path_id) const {
|
||||
return data_dirs_[path_id].get();
|
||||
}
|
||||
|
||||
bool ColumnFamilyData::ShouldPostponeFlushToRetainUDT(
|
||||
uint64_t max_memtable_id) {
|
||||
const Comparator* ucmp = user_comparator();
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
if (ts_sz == 0 || ioptions_.persist_user_defined_timestamps) {
|
||||
return false;
|
||||
}
|
||||
// If users set the `persist_user_defined_timestamps` flag to false, they
|
||||
// should also set the `full_history_ts_low` flag to indicate the range of
|
||||
// user-defined timestamps to retain in memory. Otherwise, we do not
|
||||
// explicitly postpone flush to retain UDTs.
|
||||
const std::string& full_history_ts_low = GetFullHistoryTsLow();
|
||||
if (full_history_ts_low.empty()) {
|
||||
return false;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
Slice last_table_newest_udt;
|
||||
#endif /* !NDEBUG */
|
||||
for (const Slice& table_newest_udt :
|
||||
imm()->GetTablesNewestUDT(max_memtable_id)) {
|
||||
assert(table_newest_udt.size() == full_history_ts_low.size());
|
||||
assert(last_table_newest_udt.empty() ||
|
||||
ucmp->CompareTimestamp(table_newest_udt, last_table_newest_udt) >=
|
||||
0);
|
||||
// Checking the newest UDT contained in MemTable with ascending ID up to
|
||||
// `max_memtable_id`. MemTable with bigger ID will have newer UDT, return
|
||||
// immediately on finding the first MemTable that needs postponing.
|
||||
if (ucmp->CompareTimestamp(table_newest_udt, full_history_ts_low) >= 0) {
|
||||
return true;
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
last_table_newest_udt = table_newest_udt;
|
||||
#endif /* !NDEBUG */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ColumnFamilyData::RecoverEpochNumbers() {
|
||||
assert(current_);
|
||||
auto* vstorage = current_->storage_info();
|
||||
assert(vstorage);
|
||||
vstorage->RecoverEpochNumbers(this);
|
||||
}
|
||||
|
||||
ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
const ImmutableDBOptions* db_options,
|
||||
const FileOptions& file_options,
|
||||
@@ -1644,8 +1577,8 @@ ColumnFamilyData* ColumnFamilySet::GetColumnFamily(uint32_t id) const {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnFamilyData* ColumnFamilySet::GetColumnFamily(
|
||||
const std::string& name) const {
|
||||
ColumnFamilyData* ColumnFamilySet::GetColumnFamily(const std::string& name)
|
||||
const {
|
||||
auto cfd_iter = column_families_.find(name);
|
||||
if (cfd_iter != column_families_.end()) {
|
||||
auto cfd = GetColumnFamily(cfd_iter->second);
|
||||
@@ -1681,13 +1614,6 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
db_id_, db_session_id_);
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
auto ucmp = new_cfd->user_comparator();
|
||||
assert(ucmp);
|
||||
size_t ts_sz = ucmp->timestamp_size();
|
||||
running_ts_sz_.insert({id, ts_sz});
|
||||
if (ts_sz > 0) {
|
||||
ts_sz_for_record_.insert({id, ts_sz});
|
||||
}
|
||||
max_column_family_ = std::max(max_column_family_, id);
|
||||
// add to linked list
|
||||
new_cfd->next_ = dummy_cfd_;
|
||||
@@ -1703,13 +1629,10 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
|
||||
// under a DB mutex AND from a write thread
|
||||
void ColumnFamilySet::RemoveColumnFamily(ColumnFamilyData* cfd) {
|
||||
uint32_t cf_id = cfd->GetID();
|
||||
auto cfd_iter = column_family_data_.find(cf_id);
|
||||
auto cfd_iter = column_family_data_.find(cfd->GetID());
|
||||
assert(cfd_iter != column_family_data_.end());
|
||||
column_family_data_.erase(cfd_iter);
|
||||
column_families_.erase(cfd->GetName());
|
||||
running_ts_sz_.erase(cf_id);
|
||||
ts_sz_for_record_.erase(cf_id);
|
||||
}
|
||||
|
||||
// under a DB mutex OR from a write thread
|
||||
|
||||
+23
-52
@@ -163,8 +163,8 @@ extern const double kIncSlowdownRatio;
|
||||
class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
|
||||
public:
|
||||
// create while holding the mutex
|
||||
ColumnFamilyHandleImpl(ColumnFamilyData* cfd, DBImpl* db,
|
||||
InstrumentedMutex* mutex);
|
||||
ColumnFamilyHandleImpl(
|
||||
ColumnFamilyData* cfd, DBImpl* db, InstrumentedMutex* mutex);
|
||||
// destroy without mutex
|
||||
virtual ~ColumnFamilyHandleImpl();
|
||||
virtual ColumnFamilyData* cfd() const { return cfd_; }
|
||||
@@ -189,8 +189,7 @@ class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
|
||||
class ColumnFamilyHandleInternal : public ColumnFamilyHandleImpl {
|
||||
public:
|
||||
ColumnFamilyHandleInternal()
|
||||
: ColumnFamilyHandleImpl(nullptr, nullptr, nullptr),
|
||||
internal_cfd_(nullptr) {}
|
||||
: ColumnFamilyHandleImpl(nullptr, nullptr, nullptr), internal_cfd_(nullptr) {}
|
||||
|
||||
void SetCFD(ColumnFamilyData* _cfd) { internal_cfd_ = _cfd; }
|
||||
virtual ColumnFamilyData* cfd() const override { return internal_cfd_; }
|
||||
@@ -310,6 +309,10 @@ class ColumnFamilyData {
|
||||
void SetLogNumber(uint64_t log_number) { log_number_ = log_number; }
|
||||
uint64_t GetLogNumber() const { return log_number_; }
|
||||
|
||||
void SetFlushReason(FlushReason flush_reason) {
|
||||
flush_reason_ = flush_reason;
|
||||
}
|
||||
FlushReason GetFlushReason() const { return flush_reason_; }
|
||||
// thread-safe
|
||||
const FileOptions* soptions() const;
|
||||
const ImmutableOptions* ioptions() const { return &ioptions_; }
|
||||
@@ -335,10 +338,12 @@ class ColumnFamilyData {
|
||||
// Validate CF options against DB options
|
||||
static Status ValidateOptions(const DBOptions& db_options,
|
||||
const ColumnFamilyOptions& cf_options);
|
||||
#ifndef ROCKSDB_LITE
|
||||
// REQUIRES: DB mutex held
|
||||
Status SetOptions(
|
||||
const DBOptions& db_options,
|
||||
const std::unordered_map<std::string, std::string>& options_map);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
InternalStats* internal_stats() { return internal_stats_.get(); }
|
||||
|
||||
@@ -352,7 +357,7 @@ class ColumnFamilyData {
|
||||
Version* current() { return current_; }
|
||||
Version* dummy_versions() { return dummy_versions_; }
|
||||
void SetCurrent(Version* _current);
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
|
||||
@@ -462,6 +467,12 @@ class ColumnFamilyData {
|
||||
bool queued_for_flush() { return queued_for_flush_; }
|
||||
bool queued_for_compaction() { return queued_for_compaction_; }
|
||||
|
||||
enum class WriteStallCause {
|
||||
kNone,
|
||||
kMemtableLimit,
|
||||
kL0FileCountLimit,
|
||||
kPendingCompactionBytes,
|
||||
};
|
||||
static std::pair<WriteStallCondition, WriteStallCause>
|
||||
GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
@@ -506,12 +517,6 @@ class ColumnFamilyData {
|
||||
return full_history_ts_low_;
|
||||
}
|
||||
|
||||
// REQUIRES: DB mutex held.
|
||||
// Return true if flushing up to MemTables with ID `max_memtable_id`
|
||||
// should be postponed to retain user-defined timestamps according to the
|
||||
// user's setting. Called by background flush job.
|
||||
bool ShouldPostponeFlushToRetainUDT(uint64_t max_memtable_id);
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
WriteBufferManager* write_buffer_mgr() { return write_buffer_manager_; }
|
||||
std::shared_ptr<CacheReservationManager>
|
||||
@@ -519,30 +524,14 @@ class ColumnFamilyData {
|
||||
return file_metadata_cache_res_mgr_;
|
||||
}
|
||||
|
||||
SequenceNumber GetFirstMemtableSequenceNumber() const;
|
||||
|
||||
static const uint32_t kDummyColumnFamilyDataId;
|
||||
|
||||
// Keep track of whether the mempurge feature was ever used.
|
||||
void SetMempurgeUsed() { mempurge_used_ = true; }
|
||||
bool GetMempurgeUsed() { return mempurge_used_; }
|
||||
|
||||
// Allocate and return a new epoch number
|
||||
uint64_t NewEpochNumber() { return next_epoch_number_.fetch_add(1); }
|
||||
|
||||
// Get the next epoch number to be assigned
|
||||
uint64_t GetNextEpochNumber() const { return next_epoch_number_.load(); }
|
||||
|
||||
// Set the next epoch number to be assigned
|
||||
void SetNextEpochNumber(uint64_t next_epoch_number) {
|
||||
next_epoch_number_.store(next_epoch_number);
|
||||
}
|
||||
|
||||
// Reset the next epoch number to be assigned
|
||||
void ResetNextEpochNumber() { next_epoch_number_.store(1); }
|
||||
|
||||
// Recover the next epoch number of this CF and epoch number
|
||||
// of its files (if missing)
|
||||
void RecoverEpochNumbers();
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
@@ -563,7 +552,7 @@ class ColumnFamilyData {
|
||||
Version* dummy_versions_; // Head of circular doubly-linked list of versions.
|
||||
Version* current_; // == dummy_versions->prev_
|
||||
|
||||
std::atomic<int> refs_; // outstanding references to ColumnFamilyData
|
||||
std::atomic<int> refs_; // outstanding references to ColumnFamilyData
|
||||
std::atomic<bool> initialized_;
|
||||
std::atomic<bool> dropped_; // true if client dropped it
|
||||
|
||||
@@ -608,6 +597,8 @@ class ColumnFamilyData {
|
||||
// recovered from
|
||||
uint64_t log_number_;
|
||||
|
||||
std::atomic<FlushReason> flush_reason_;
|
||||
|
||||
// An object that keeps all the compaction stats
|
||||
// and picks the next compaction
|
||||
std::unique_ptr<CompactionPicker> compaction_picker_;
|
||||
@@ -642,8 +633,6 @@ class ColumnFamilyData {
|
||||
// a Version associated with this CFD
|
||||
std::shared_ptr<CacheReservationManager> file_metadata_cache_res_mgr_;
|
||||
bool mempurge_used_;
|
||||
|
||||
std::atomic<uint64_t> next_epoch_number_;
|
||||
};
|
||||
|
||||
// ColumnFamilySet has interesting thread-safety requirements
|
||||
@@ -667,7 +656,8 @@ class ColumnFamilySet {
|
||||
// ColumnFamilySet supports iteration
|
||||
class iterator {
|
||||
public:
|
||||
explicit iterator(ColumnFamilyData* cfd) : current_(cfd) {}
|
||||
explicit iterator(ColumnFamilyData* cfd)
|
||||
: current_(cfd) {}
|
||||
// NOTE: minimum operators for for-loop iteration
|
||||
iterator& operator++() {
|
||||
current_ = current_->next_;
|
||||
@@ -709,16 +699,6 @@ class ColumnFamilySet {
|
||||
Version* dummy_version,
|
||||
const ColumnFamilyOptions& options);
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>& GetRunningColumnFamiliesTimestampSize()
|
||||
const {
|
||||
return running_ts_sz_;
|
||||
}
|
||||
|
||||
const UnorderedMap<uint32_t, size_t>&
|
||||
GetColumnFamiliesTimestampSizeForRecord() const {
|
||||
return ts_sz_for_record_;
|
||||
}
|
||||
|
||||
iterator begin() { return iterator(dummy_cfd_->next_); }
|
||||
iterator end() { return iterator(dummy_cfd_); }
|
||||
|
||||
@@ -744,15 +724,6 @@ class ColumnFamilySet {
|
||||
UnorderedMap<std::string, uint32_t> column_families_;
|
||||
UnorderedMap<uint32_t, ColumnFamilyData*> column_family_data_;
|
||||
|
||||
// Mutating / reading `running_ts_sz_` and `ts_sz_for_record_` follow
|
||||
// the same requirements as `column_families_` and `column_family_data_`.
|
||||
// Mapping from column family id to user-defined timestamp size for all
|
||||
// running column families.
|
||||
UnorderedMap<uint32_t, size_t> running_ts_sz_;
|
||||
// Mapping from column family id to user-defined timestamp size for
|
||||
// column families with non-zero user-defined timestamp size.
|
||||
UnorderedMap<uint32_t, size_t> ts_sz_for_record_;
|
||||
|
||||
uint32_t max_column_family_;
|
||||
const FileOptions file_options_;
|
||||
|
||||
|
||||
+97
-226
@@ -17,7 +17,6 @@
|
||||
#include "options/options_parser.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -40,7 +39,9 @@ class EnvCounter : public SpecialEnv {
|
||||
public:
|
||||
explicit EnvCounter(Env* base)
|
||||
: SpecialEnv(base), num_new_writable_file_(0) {}
|
||||
int GetNumberOfNewWritableFileCalls() { return num_new_writable_file_; }
|
||||
int GetNumberOfNewWritableFileCalls() {
|
||||
return num_new_writable_file_;
|
||||
}
|
||||
Status NewWritableFile(const std::string& f, std::unique_ptr<WritableFile>* r,
|
||||
const EnvOptions& soptions) override {
|
||||
++num_new_writable_file_;
|
||||
@@ -64,9 +65,6 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
db_options_.create_if_missing = true;
|
||||
db_options_.fail_if_options_file_error = true;
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
void SetUp() override {
|
||||
EXPECT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_)));
|
||||
}
|
||||
|
||||
@@ -75,7 +73,11 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
for (auto h : handles_) {
|
||||
ColumnFamilyDescriptor cfdescriptor;
|
||||
Status s = h->GetDescriptor(&cfdescriptor);
|
||||
#ifdef ROCKSDB_LITE
|
||||
EXPECT_TRUE(s.IsNotSupported());
|
||||
#else
|
||||
EXPECT_OK(s);
|
||||
#endif // ROCKSDB_LITE
|
||||
column_families.push_back(cfdescriptor);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -185,7 +187,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
}
|
||||
|
||||
Status OpenReadOnly(std::vector<std::string> cf,
|
||||
std::vector<ColumnFamilyOptions> options = {}) {
|
||||
std::vector<ColumnFamilyOptions> options = {}) {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
names_.clear();
|
||||
for (size_t i = 0; i < cf.size(); ++i) {
|
||||
@@ -197,17 +199,22 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
&db_);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // ReadOnlyDB is not supported
|
||||
void AssertOpenReadOnly(std::vector<std::string> cf,
|
||||
std::vector<ColumnFamilyOptions> options = {}) {
|
||||
std::vector<ColumnFamilyOptions> options = {}) {
|
||||
ASSERT_OK(OpenReadOnly(cf, options));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
|
||||
void Open(std::vector<std::string> cf,
|
||||
std::vector<ColumnFamilyOptions> options = {}) {
|
||||
ASSERT_OK(TryOpen(cf, options));
|
||||
}
|
||||
|
||||
void Open() { Open({"default"}); }
|
||||
void Open() {
|
||||
Open({"default"});
|
||||
}
|
||||
|
||||
DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_); }
|
||||
|
||||
@@ -222,20 +229,31 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
}
|
||||
|
||||
bool IsDbWriteStopped() {
|
||||
#ifndef ROCKSDB_LITE
|
||||
uint64_t v;
|
||||
EXPECT_TRUE(dbfull()->GetIntProperty("rocksdb.is-write-stopped", &v));
|
||||
return (v == 1);
|
||||
#else
|
||||
return dbfull()->TEST_write_controler().IsStopped();
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
uint64_t GetDbDelayedWriteRate() {
|
||||
#ifndef ROCKSDB_LITE
|
||||
uint64_t v;
|
||||
EXPECT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.actual-delayed-write-rate", &v));
|
||||
return v;
|
||||
#else
|
||||
if (!dbfull()->TEST_write_controler().NeedsDelay()) {
|
||||
return 0;
|
||||
}
|
||||
return dbfull()->TEST_write_controler().delayed_write_rate();
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void Destroy(const std::vector<ColumnFamilyDescriptor>& column_families =
|
||||
std::vector<ColumnFamilyDescriptor>()) {
|
||||
std::vector<ColumnFamilyDescriptor>()) {
|
||||
Close();
|
||||
ASSERT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_),
|
||||
column_families));
|
||||
@@ -254,6 +272,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
db_->CreateColumnFamily(current_cf_opt, cfs[i], &handles_[cfi]));
|
||||
names_[cfi] = cfs[i];
|
||||
|
||||
#ifndef ROCKSDB_LITE // RocksDBLite does not support GetDescriptor
|
||||
// Verify the CF options of the returned CF handle.
|
||||
ColumnFamilyDescriptor desc;
|
||||
ASSERT_OK(handles_[cfi]->GetDescriptor(&desc));
|
||||
@@ -262,6 +281,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
|
||||
ConfigOptions(), desc.options,
|
||||
SanitizeOptions(dbfull()->immutable_db_options(), current_cf_opt)));
|
||||
#endif // !ROCKSDB_LITE
|
||||
cfi++;
|
||||
}
|
||||
}
|
||||
@@ -310,11 +330,14 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
ASSERT_OK(db_->FlushWAL(/*sync=*/false));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // TEST functions in DB are not supported in lite
|
||||
void WaitForFlush(int cf) {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable(handles_[cf]));
|
||||
}
|
||||
|
||||
void WaitForCompaction() { ASSERT_OK(dbfull()->TEST_WaitForCompact()); }
|
||||
void WaitForCompaction() {
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
|
||||
uint64_t MaxTotalInMemoryState() {
|
||||
return dbfull()->TEST_MaxTotalInMemoryState();
|
||||
@@ -323,6 +346,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
void AssertMaxTotalInMemoryState(uint64_t value) {
|
||||
ASSERT_EQ(value, MaxTotalInMemoryState());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
Status Put(int cf, const std::string& key, const std::string& value) {
|
||||
return db_->Put(WriteOptions(), handles_[cf], Slice(key), Slice(value));
|
||||
@@ -330,7 +354,9 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
Status Merge(int cf, const std::string& key, const std::string& value) {
|
||||
return db_->Merge(WriteOptions(), handles_[cf], Slice(key), Slice(value));
|
||||
}
|
||||
Status Flush(int cf) { return db_->Flush(FlushOptions(), handles_[cf]); }
|
||||
Status Flush(int cf) {
|
||||
return db_->Flush(FlushOptions(), handles_[cf]);
|
||||
}
|
||||
|
||||
std::string Get(int cf, const std::string& key) {
|
||||
ReadOptions options;
|
||||
@@ -360,6 +386,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
"rocksdb.num-files-at-level" + std::to_string(level));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel(int cf) {
|
||||
std::string result;
|
||||
@@ -376,19 +403,31 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
result.resize(last_non_zero_offset);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
void AssertFilesPerLevel(const std::string& value, int cf) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(value, FilesPerLevel(cf));
|
||||
#else
|
||||
(void) value;
|
||||
(void) cf;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // GetLiveFilesMetaData is not supported
|
||||
int CountLiveFiles() {
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
return static_cast<int>(metadata.size());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void AssertCountLiveFiles(int expected_value) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(expected_value, CountLiveFiles());
|
||||
#else
|
||||
(void) expected_value;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Do n memtable flushes, each of which produces an sstable
|
||||
@@ -402,6 +441,7 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // GetSortedWalFiles is not supported
|
||||
int CountLiveLogFiles() {
|
||||
int micros_wait_for_log_deletion = 20000;
|
||||
env_->SleepForMicroseconds(micros_wait_for_log_deletion);
|
||||
@@ -430,18 +470,25 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void AssertCountLiveLogFiles(int value) {
|
||||
#ifndef ROCKSDB_LITE // GetSortedWalFiles is not supported
|
||||
ASSERT_EQ(value, CountLiveLogFiles());
|
||||
#else
|
||||
(void) value;
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void AssertNumberOfImmutableMemtables(std::vector<int> num_per_cf) {
|
||||
assert(num_per_cf.size() == handles_.size());
|
||||
|
||||
#ifndef ROCKSDB_LITE // GetProperty is not supported in lite
|
||||
for (size_t i = 0; i < num_per_cf.size(); ++i) {
|
||||
ASSERT_EQ(num_per_cf[i], GetProperty(static_cast<int>(i),
|
||||
"rocksdb.num-immutable-mem-table"));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void CopyFile(const std::string& source, const std::string& destination,
|
||||
@@ -474,14 +521,14 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
return static_cast<int>(files.size());
|
||||
}
|
||||
|
||||
void RecalculateWriteStallConditions(
|
||||
ColumnFamilyData* cfd, const MutableCFOptions& mutable_cf_options) {
|
||||
void RecalculateWriteStallConditions(ColumnFamilyData* cfd,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
// add lock to avoid race condition between
|
||||
// `RecalculateWriteStallConditions` which writes to CFStats and
|
||||
// background `DBImpl::DumpStats()` threads which read CFStats
|
||||
dbfull()->TEST_LockMutex();
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
dbfull()->TEST_UnlockMutex();
|
||||
dbfull()-> TEST_UnlockMutex();
|
||||
}
|
||||
|
||||
std::vector<ColumnFamilyHandle*> handles_;
|
||||
@@ -537,6 +584,7 @@ TEST_P(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_P(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) {
|
||||
Open();
|
||||
|
||||
@@ -559,6 +607,7 @@ TEST_P(ColumnFamilyTest, CreateCFRaceWithGetAggProperty) {
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class FlushEmptyCFTestWithParam
|
||||
: public ColumnFamilyTestBase,
|
||||
@@ -902,6 +951,7 @@ TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // TEST functions used are not supported
|
||||
TEST_P(ColumnFamilyTest, FlushTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
@@ -920,7 +970,8 @@ TEST_P(ColumnFamilyTest, FlushTest) {
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
uint64_t max_total_in_memory_state = MaxTotalInMemoryState();
|
||||
uint64_t max_total_in_memory_state =
|
||||
MaxTotalInMemoryState();
|
||||
ASSERT_OK(Flush(i));
|
||||
AssertMaxTotalInMemoryState(max_total_in_memory_state);
|
||||
}
|
||||
@@ -1016,6 +1067,7 @@ TEST_P(ColumnFamilyTest, LogDeletionTest) {
|
||||
AssertCountLiveLogFiles(4);
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_P(ColumnFamilyTest, CrashAfterFlush) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
@@ -1055,6 +1107,7 @@ TEST_P(ColumnFamilyTest, OpenNonexistentColumnFamily) {
|
||||
ASSERT_TRUE(TryOpen({"default", "dne"}).IsInvalidArgument());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
|
||||
// Makes sure that obsolete log files get deleted
|
||||
TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
// disable flushing stale column families
|
||||
@@ -1156,18 +1209,20 @@ TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
WaitForFlush(2);
|
||||
AssertNumberOfImmutableMemtables({0, 0, 0, 0});
|
||||
AssertCountLiveLogFiles(12);
|
||||
PutRandomData(1, 2 * 200, 1000);
|
||||
PutRandomData(1, 2*200, 1000);
|
||||
WaitForFlush(1);
|
||||
AssertNumberOfImmutableMemtables({0, 0, 0, 0});
|
||||
AssertCountLiveLogFiles(7);
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// The test is commented out because we want to test that snapshot is
|
||||
// not created for memtables not supported it, but There isn't a memtable
|
||||
// that doesn't support snapshot right now. If we have one later, we can
|
||||
// re-enable the test.
|
||||
//
|
||||
// #ifndef ROCKSDB_LITE // Cuckoo is not supported in lite
|
||||
// TEST_P(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
// db_options_.allow_concurrent_memtable_write = false;
|
||||
// Open();
|
||||
@@ -1187,6 +1242,7 @@ TEST_P(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
// {second}); auto* s3 = dbfull()->GetSnapshot(); ASSERT_TRUE(s3 == nullptr);
|
||||
// Close();
|
||||
// }
|
||||
// #endif // !ROCKSDB_LITE
|
||||
|
||||
class TestComparator : public Comparator {
|
||||
int Compare(const ROCKSDB_NAMESPACE::Slice& /*a*/,
|
||||
@@ -1253,13 +1309,13 @@ TEST_P(ColumnFamilyTest, DifferentMergeOperators) {
|
||||
Close();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
|
||||
TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
ColumnFamilyOptions default_cf, one, two;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
@@ -1277,7 +1333,6 @@ TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
two.level_compaction_dynamic_level_bytes = false;
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
@@ -1322,7 +1377,9 @@ TEST_P(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Sync points not supported in RocksDB Lite
|
||||
|
||||
TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
@@ -1332,7 +1389,6 @@ TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
@@ -1349,7 +1405,6 @@ TEST_P(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
two.level_compaction_dynamic_level_bytes = false;
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
@@ -1432,14 +1487,13 @@ TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1450,7 +1504,6 @@ TEST_P(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
two.level_compaction_dynamic_level_bytes = false;
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
@@ -1529,14 +1582,13 @@ TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
default_cf.level_compaction_dynamic_level_bytes = false;
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options = GetBlockBasedTableOptions();
|
||||
|
||||
;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -1547,7 +1599,6 @@ TEST_P(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
two.level_compaction_dynamic_level_bytes = false;
|
||||
two.compaction_style = kCompactionStyleLevel;
|
||||
two.num_levels = 4;
|
||||
two.level0_file_num_compaction_trigger = 3;
|
||||
@@ -1992,7 +2043,9 @@ TEST_P(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // Tailing iterator not supported
|
||||
namespace {
|
||||
std::string IterStatus(Iterator* iter) {
|
||||
std::string result;
|
||||
@@ -2050,7 +2103,9 @@ TEST_P(ColumnFamilyTest, NewIteratorsTest) {
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // ReadOnlyDB is not supported
|
||||
TEST_P(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two", "three", "four"});
|
||||
@@ -2068,6 +2123,7 @@ TEST_P(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
ASSERT_EQ("bla", Get(1, "foo"));
|
||||
ASSERT_EQ("blablablabla", Get(2, "foo"));
|
||||
|
||||
|
||||
// test newiterators
|
||||
{
|
||||
std::vector<Iterator*> iterators;
|
||||
@@ -2099,7 +2155,9 @@ TEST_P(ColumnFamilyTest, ReadOnlyDBTest) {
|
||||
s = OpenReadOnly({"one", "four"});
|
||||
ASSERT_TRUE(!s.ok());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported in lite
|
||||
TEST_P(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two", "three", "four"});
|
||||
@@ -2121,7 +2179,9 @@ TEST_P(ColumnFamilyTest, DontRollEmptyLogs) {
|
||||
ASSERT_EQ(static_cast<size_t>(total_new_writable_files), handles_.size() + 1);
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForCompaction() is not supported in lite
|
||||
TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
@@ -2168,6 +2228,7 @@ TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
ASSERT_EQ(0, dbfull()->TEST_total_log_size());
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_P(ColumnFamilyTest, CreateMissingColumnFamilies) {
|
||||
Status s = TryOpen({"one", "two"});
|
||||
@@ -2407,6 +2468,8 @@ TEST_P(ColumnFamilyTest, FlushAndDropRaceCondition) {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// skipped as persisting options is not supported in ROCKSDB_LITE
|
||||
namespace {
|
||||
std::atomic<int> test_stage(0);
|
||||
std::atomic<bool> ordered_by_writethread(false);
|
||||
@@ -2425,7 +2488,7 @@ void DropSingleColumnFamily(ColumnFamilyTest* cf_test, int cf_id,
|
||||
}
|
||||
test_stage = kChildThreadFinishDroppingColumnFamily;
|
||||
}
|
||||
} // anonymous namespace
|
||||
} // namespace
|
||||
|
||||
TEST_P(ColumnFamilyTest, CreateAndDropRace) {
|
||||
const int kCfCount = 5;
|
||||
@@ -2488,6 +2551,7 @@ TEST_P(ColumnFamilyTest, CreateAndDropRace) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_P(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
const uint64_t kBaseRate = 800000u;
|
||||
@@ -2897,6 +2961,7 @@ TEST_P(ColumnFamilyTest, CreateDropAndDestroy) {
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_P(ColumnFamilyTest, CreateDropAndDestroyWithoutFileDeletion) {
|
||||
ColumnFamilyHandle* cfh;
|
||||
Open();
|
||||
@@ -2951,7 +3016,9 @@ TEST_P(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
db_options_.env = env_;
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // WaitForFlush() is not supported
|
||||
TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
SpecialEnv env(Env::Default());
|
||||
db_options_.env = &env;
|
||||
@@ -3058,7 +3125,9 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
db_options_.env = env_;
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#ifndef ROCKSDB_LITE // TEST functions are not supported in lite
|
||||
TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
SpecialEnv env(Env::Default());
|
||||
// Allow both of flush and purge job to schedule.
|
||||
@@ -3134,6 +3203,7 @@ TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
db_options_.env = env_;
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Disable on windows because SyncWAL requires env->IsSyncThreadSafe()
|
||||
// to return true which is not so in unbuffered mode.
|
||||
@@ -3384,205 +3454,6 @@ TEST(ColumnFamilyTest, ValidateMemtableKVChecksumOption) {
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
}
|
||||
|
||||
// Tests the flushing behavior of a column family to retain user-defined
|
||||
// timestamp when `persist_user_defined_timestamp` is false.
|
||||
class ColumnFamilyRetainUDTTest : public ColumnFamilyTestBase {
|
||||
public:
|
||||
ColumnFamilyRetainUDTTest() : ColumnFamilyTestBase(kLatestFormatVersion) {}
|
||||
|
||||
void SetUp() override {
|
||||
db_options_.allow_concurrent_memtable_write = false;
|
||||
column_family_options_.comparator =
|
||||
test::BytewiseComparatorWithU64TsWrapper();
|
||||
column_family_options_.persist_user_defined_timestamps = false;
|
||||
ColumnFamilyTestBase::SetUp();
|
||||
}
|
||||
|
||||
Status Put(int cf, const std::string& key, const std::string& ts,
|
||||
const std::string& value) {
|
||||
return db_->Put(WriteOptions(), handles_[cf], Slice(key), Slice(ts),
|
||||
Slice(value));
|
||||
}
|
||||
};
|
||||
|
||||
class TestTsComparator : public Comparator {
|
||||
public:
|
||||
TestTsComparator() : Comparator(8 /*ts_sz*/) {}
|
||||
|
||||
int Compare(const ROCKSDB_NAMESPACE::Slice& /*a*/,
|
||||
const ROCKSDB_NAMESPACE::Slice& /*b*/) const override {
|
||||
return 0;
|
||||
}
|
||||
const char* Name() const override { return "TestTs"; }
|
||||
void FindShortestSeparator(
|
||||
std::string* /*start*/,
|
||||
const ROCKSDB_NAMESPACE::Slice& /*limit*/) const override {}
|
||||
void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, SanityCheck) {
|
||||
Open();
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.persist_user_defined_timestamps = false;
|
||||
TestTsComparator test_comparator;
|
||||
cf_options.comparator = &test_comparator;
|
||||
ColumnFamilyHandle* handle;
|
||||
// Not persisting user-defined timestamps feature only supports user-defined
|
||||
// timestamps formatted as uint64_t.
|
||||
ASSERT_TRUE(
|
||||
db_->CreateColumnFamily(cf_options, "pikachu", &handle).IsNotSupported());
|
||||
|
||||
Destroy();
|
||||
// Not persisting user-defined timestamps feature doesn't work in combination
|
||||
// with atomic flush.
|
||||
db_options_.atomic_flush = true;
|
||||
ASSERT_TRUE(TryOpen({"default"}).IsNotSupported());
|
||||
|
||||
// Not persisting user-defined timestamps feature doesn't work in combination
|
||||
// with concurrent memtable write.
|
||||
db_options_.atomic_flush = false;
|
||||
db_options_.allow_concurrent_memtable_write = true;
|
||||
ASSERT_TRUE(TryOpen({"default"}).IsNotSupported());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, FullHistoryTsLowNotSet) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
// No `full_history_ts_low` explicitly set by user, flush is continued
|
||||
// without checking if its UDTs expired.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// After flush, `full_history_ts_low` should be automatically advanced to
|
||||
// the effective cutoff timestamp: write_ts + 1
|
||||
std::string cutoff_ts;
|
||||
PutFixed64(&cutoff_ts, 2);
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, AllKeysExpired) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
std::string cutoff_ts;
|
||||
PutFixed64(&cutoff_ts, 3);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
// All keys expired w.r.t the configured `full_history_ts_low`, flush continue
|
||||
// without the need for a re-schedule.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// `full_history_ts_low` stays unchanged after flush.
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
TEST_F(ColumnFamilyRetainUDTTest, NotAllKeysExpiredFlushToAvoidWriteStall) {
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(1, reschedule_count);
|
||||
});
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
Open();
|
||||
std::string cutoff_ts;
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
PutFixed64(&cutoff_ts, 1);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
ASSERT_OK(db_->SetOptions(handles_[0], {{"max_write_buffer_number", "1"}}));
|
||||
// Not all keys expired, but flush is continued without a re-schedule because
|
||||
// of risk of write stall.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// After flush, `full_history_ts_low` should be automatically advanced to
|
||||
// the effective cutoff timestamp: write_ts + 1
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
|
||||
cutoff_ts.clear();
|
||||
PutFixed64(&cutoff_ts, 2);
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyRetainUDTTest, NotAllKeysExpiredFlushRescheduled) {
|
||||
std::string cutoff_ts;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::AfterRetainUDTReschedule:cb", [&](void* /*arg*/) {
|
||||
// Increasing full_history_ts_low so all keys expired after the initial
|
||||
// FlushRequest is rescheduled
|
||||
cutoff_ts.clear();
|
||||
PutFixed64(&cutoff_ts, 3);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
});
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundFlush:CheckFlushRequest:cb", [&](void* arg) {
|
||||
ASSERT_NE(nullptr, arg);
|
||||
auto reschedule_count = *static_cast<int*>(arg);
|
||||
ASSERT_EQ(2, reschedule_count);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Open();
|
||||
std::string write_ts;
|
||||
PutFixed64(&write_ts, 1);
|
||||
ASSERT_OK(Put(0, "foo", write_ts, "v1"));
|
||||
PutFixed64(&cutoff_ts, 1);
|
||||
ASSERT_OK(db_->IncreaseFullHistoryTsLow(handles_[0], cutoff_ts));
|
||||
// Not all keys expired, and there is no risk of write stall. Flush is
|
||||
// rescheduled. The actual flush happens after `full_history_ts_low` is
|
||||
// increased to mark all keys expired.
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
std::string effective_full_history_ts_low;
|
||||
ASSERT_OK(
|
||||
db_->GetFullHistoryTsLow(handles_[0], &effective_full_history_ts_low));
|
||||
// `full_history_ts_low` stays unchanged.
|
||||
ASSERT_EQ(cutoff_ts, effective_full_history_ts_low);
|
||||
Close();
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+23
-11
@@ -3,6 +3,7 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@@ -66,7 +67,6 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
const int kWriteBufferSize = 10000;
|
||||
const int kLevel0Trigger = 2;
|
||||
options.create_if_missing = true;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
// Small slowdown and stop trigger for experimental purpose.
|
||||
options.level0_slowdown_writes_trigger = 20;
|
||||
@@ -121,9 +121,7 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
TEST_F(CompactFilesTest, MultipleLevel) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
// Otherwise background compaction can happen to
|
||||
// drain unnecessary level
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = 6;
|
||||
// Add listener
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
@@ -184,6 +182,7 @@ TEST_F(CompactFilesTest, MultipleLevel) {
|
||||
for (int invalid_output_level = 0; invalid_output_level < 5;
|
||||
invalid_output_level++) {
|
||||
s = db->CompactFiles(CompactionOptions(), files, invalid_output_level);
|
||||
std::cout << s.ToString() << std::endl;
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
}
|
||||
|
||||
@@ -345,11 +344,13 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
return true;
|
||||
}
|
||||
std::string res;
|
||||
EXPECT_TRUE(db_->Get(ReadOptions(), "", &res).IsNotFound());
|
||||
db_->Get(ReadOptions(), "", &res);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetDB(DB* db) { db_ = db; }
|
||||
void SetDB(DB* db) {
|
||||
db_ = db;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "FilterWithGet"; }
|
||||
|
||||
@@ -357,10 +358,10 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
DB* db_;
|
||||
};
|
||||
|
||||
|
||||
std::shared_ptr<FilterWithGet> cf(new FilterWithGet());
|
||||
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_filter = cf.get();
|
||||
|
||||
@@ -384,6 +385,7 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
db->CompactFiles(ROCKSDB_NAMESPACE::CompactionOptions(), {fname}, 0));
|
||||
}
|
||||
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
@@ -398,12 +400,12 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
|
||||
}
|
||||
// Check that passing `CompressionType::kDisableCompressionOption` to
|
||||
// `CompactFiles` causes it to use the column family compression options.
|
||||
for (auto compaction_style : {CompactionStyle::kCompactionStyleLevel,
|
||||
CompactionStyle::kCompactionStyleUniversal,
|
||||
CompactionStyle::kCompactionStyleNone}) {
|
||||
for (auto compaction_style :
|
||||
{CompactionStyle::kCompactionStyleLevel,
|
||||
CompactionStyle::kCompactionStyleUniversal,
|
||||
CompactionStyle::kCompactionStyleNone}) {
|
||||
ASSERT_OK(DestroyDB(db_name_, Options()));
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.compaction_style = compaction_style;
|
||||
// L0: Snappy, L1: ZSTD, L2: Snappy
|
||||
options.compression_per_level = {CompressionType::kSnappyCompression,
|
||||
@@ -493,3 +495,13 @@ int main(int argc, char** argv) {
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as DBImpl::CompactFiles is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -188,11 +188,6 @@ class ClippingIterator : public InternalIterator {
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
bool IsDeleteRangeSentinelKey() const override {
|
||||
assert(valid_);
|
||||
return iter_->IsDeleteRangeSentinelKey();
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateValid() {
|
||||
assert(!iter_->Valid() || iter_->status().ok());
|
||||
|
||||
+57
-129
@@ -13,7 +13,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "logging/logging.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "rocksdb/sst_partitioner.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -24,13 +23,14 @@ namespace ROCKSDB_NAMESPACE {
|
||||
const uint64_t kRangeTombstoneSentinel =
|
||||
PackSequenceAndType(kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
|
||||
int sstableKeyCompare(const Comparator* uc, const Slice& a, const Slice& b) {
|
||||
auto c = uc->CompareWithoutTimestamp(ExtractUserKey(a), ExtractUserKey(b));
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b) {
|
||||
auto c = user_cmp->CompareWithoutTimestamp(a.user_key(), b.user_key());
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
auto a_footer = ExtractInternalKeyFooter(a);
|
||||
auto b_footer = ExtractInternalKeyFooter(b);
|
||||
auto a_footer = ExtractInternalKeyFooter(a.Encode());
|
||||
auto b_footer = ExtractInternalKeyFooter(b.Encode());
|
||||
if (a_footer == kRangeTombstoneSentinel) {
|
||||
if (b_footer != kRangeTombstoneSentinel) {
|
||||
return -1;
|
||||
@@ -204,34 +204,6 @@ bool Compaction::IsFullCompaction(
|
||||
return num_files_in_compaction == total_num_files;
|
||||
}
|
||||
|
||||
const TablePropertiesCollection& Compaction::GetTableProperties() {
|
||||
if (!input_table_properties_initialized_) {
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
for (size_t i = 0; i < num_input_levels(); ++i) {
|
||||
for (const FileMetaData* fmd : *(this->inputs(i))) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
std::string file_name =
|
||||
TableFileName(immutable_options_.cf_paths, fmd->fd.GetNumber(),
|
||||
fmd->fd.GetPathId());
|
||||
Status s = input_version_->GetTableProperties(read_options, &tp, fmd,
|
||||
&file_name);
|
||||
if (s.ok()) {
|
||||
table_properties_[file_name] = tp;
|
||||
} else {
|
||||
ROCKS_LOG_ERROR(immutable_options_.info_log,
|
||||
"Unable to load table properties for file %" PRIu64
|
||||
" --- %s\n",
|
||||
fmd->fd.GetNumber(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input_table_properties_initialized_ = true;
|
||||
};
|
||||
|
||||
return table_properties_;
|
||||
}
|
||||
|
||||
Compaction::Compaction(
|
||||
VersionStorageInfo* vstorage, const ImmutableOptions& _immutable_options,
|
||||
const MutableCFOptions& _mutable_cf_options,
|
||||
@@ -266,19 +238,12 @@ Compaction::Compaction(
|
||||
inputs_(PopulateWithAtomicBoundaries(vstorage, std::move(_inputs))),
|
||||
grandparents_(std::move(_grandparents)),
|
||||
score_(_score),
|
||||
bottommost_level_(
|
||||
// For simplicity, we don't support the concept of "bottommost level"
|
||||
// with
|
||||
// `CompactionReason::kExternalSstIngestion` and
|
||||
// `CompactionReason::kRefitLevel`
|
||||
(_compaction_reason == CompactionReason::kExternalSstIngestion ||
|
||||
_compaction_reason == CompactionReason::kRefitLevel)
|
||||
? false
|
||||
: IsBottommostLevel(output_level_, vstorage, inputs_)),
|
||||
bottommost_level_(IsBottommostLevel(output_level_, vstorage, inputs_)),
|
||||
is_full_compaction_(IsFullCompaction(vstorage, inputs_)),
|
||||
is_manual_compaction_(_manual_compaction),
|
||||
trim_ts_(_trim_ts),
|
||||
is_trivial_move_(false),
|
||||
|
||||
compaction_reason_(_compaction_reason),
|
||||
notify_on_compaction_completion_(false),
|
||||
enable_blob_garbage_collection_(
|
||||
@@ -293,15 +258,8 @@ Compaction::Compaction(
|
||||
_blob_garbage_collection_age_cutoff > 1
|
||||
? mutable_cf_options()->blob_garbage_collection_age_cutoff
|
||||
: _blob_garbage_collection_age_cutoff),
|
||||
penultimate_level_(
|
||||
// For simplicity, we don't support the concept of "penultimate level"
|
||||
// with `CompactionReason::kExternalSstIngestion` and
|
||||
// `CompactionReason::kRefitLevel`
|
||||
_compaction_reason == CompactionReason::kExternalSstIngestion ||
|
||||
_compaction_reason == CompactionReason::kRefitLevel
|
||||
? Compaction::kInvalidLevel
|
||||
: EvaluatePenultimateLevel(vstorage, immutable_options_,
|
||||
start_level_, output_level_)) {
|
||||
penultimate_level_(EvaluatePenultimateLevel(
|
||||
vstorage, immutable_options_, start_level_, output_level_)) {
|
||||
MarkFilesBeingCompacted(true);
|
||||
if (is_manual_compaction_) {
|
||||
compaction_reason_ = CompactionReason::kManualCompaction;
|
||||
@@ -374,7 +332,6 @@ void Compaction::PopulatePenultimateLevelOutputRange() {
|
||||
// the case that the penultimate level is empty).
|
||||
if (immutable_options_.compaction_style == kCompactionStyleUniversal) {
|
||||
exclude_level = kInvalidLevel;
|
||||
penultimate_output_range_type_ = PenultimateOutputRangeType::kFullRange;
|
||||
std::set<uint64_t> penultimate_inputs;
|
||||
for (const auto& input_lvl : inputs_) {
|
||||
if (input_lvl.level == penultimate_level_) {
|
||||
@@ -388,8 +345,7 @@ void Compaction::PopulatePenultimateLevelOutputRange() {
|
||||
if (penultimate_inputs.find(file->fd.GetNumber()) ==
|
||||
penultimate_inputs.end()) {
|
||||
exclude_level = number_levels_ - 1;
|
||||
penultimate_output_range_type_ =
|
||||
PenultimateOutputRangeType::kNonLastRange;
|
||||
penultimate_output_range_type_ = PenultimateOutputRangeType::kFullRange;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -398,6 +354,35 @@ void Compaction::PopulatePenultimateLevelOutputRange() {
|
||||
GetBoundaryKeys(input_vstorage_, inputs_,
|
||||
&penultimate_level_smallest_user_key_,
|
||||
&penultimate_level_largest_user_key_, exclude_level);
|
||||
|
||||
// If there's a case that the penultimate level output range is overlapping
|
||||
// with the existing files, disable the penultimate level output by setting
|
||||
// the range to empty. One example is the range delete could have overlap
|
||||
// boundary with the next file. (which is actually a false overlap)
|
||||
// TODO: Exclude such false overlap, so it won't disable the penultimate
|
||||
// output.
|
||||
std::set<uint64_t> penultimate_inputs;
|
||||
for (const auto& input_lvl : inputs_) {
|
||||
if (input_lvl.level == penultimate_level_) {
|
||||
for (const auto& file : input_lvl.files) {
|
||||
penultimate_inputs.emplace(file->fd.GetNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto penultimate_files = input_vstorage_->LevelFiles(penultimate_level_);
|
||||
for (const auto& file : penultimate_files) {
|
||||
if (penultimate_inputs.find(file->fd.GetNumber()) ==
|
||||
penultimate_inputs.end() &&
|
||||
OverlapPenultimateLevelOutputRange(file->smallest.user_key(),
|
||||
file->largest.user_key())) {
|
||||
// basically disable the penultimate range output. which should be rare
|
||||
// or a false overlap caused by range del
|
||||
penultimate_level_smallest_user_key_ = "";
|
||||
penultimate_level_largest_user_key_ = "";
|
||||
penultimate_output_range_type_ = PenultimateOutputRangeType::kDisabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Compaction::~Compaction() {
|
||||
@@ -493,11 +478,6 @@ bool Compaction::IsTrivialMove() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (compaction_reason_ == CompactionReason::kChangeTemperature) {
|
||||
// Changing temperature usually requires rewriting the file.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Used in universal compaction, where trivial move can be done if the
|
||||
// input files are non overlapping
|
||||
if ((mutable_cf_options_.compaction_options_universal.allow_trivial_move) &&
|
||||
@@ -514,24 +494,25 @@ bool Compaction::IsTrivialMove() const {
|
||||
|
||||
// assert inputs_.size() == 1
|
||||
|
||||
if (output_level_ + 1 < number_levels_) {
|
||||
std::unique_ptr<SstPartitioner> partitioner = CreateSstPartitioner();
|
||||
for (const auto& file : inputs_.front().files) {
|
||||
std::vector<FileMetaData*> file_grand_parents;
|
||||
input_vstorage_->GetOverlappingInputs(output_level_ + 1, &file->smallest,
|
||||
&file->largest,
|
||||
&file_grand_parents);
|
||||
const auto compaction_size =
|
||||
file->fd.GetFileSize() + TotalFileSize(file_grand_parents);
|
||||
if (compaction_size > max_compaction_bytes_) {
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<SstPartitioner> partitioner = CreateSstPartitioner();
|
||||
|
||||
if (partitioner.get() != nullptr) {
|
||||
if (!partitioner->CanDoTrivialMove(file->smallest.user_key(),
|
||||
file->largest.user_key())) {
|
||||
return false;
|
||||
}
|
||||
for (const auto& file : inputs_.front().files) {
|
||||
std::vector<FileMetaData*> file_grand_parents;
|
||||
if (output_level_ + 1 >= number_levels_) {
|
||||
continue;
|
||||
}
|
||||
input_vstorage_->GetOverlappingInputs(output_level_ + 1, &file->smallest,
|
||||
&file->largest, &file_grand_parents);
|
||||
const auto compaction_size =
|
||||
file->fd.GetFileSize() + TotalFileSize(file_grand_parents);
|
||||
if (compaction_size > max_compaction_bytes_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (partitioner.get() != nullptr) {
|
||||
if (!partitioner->CanDoTrivialMove(file->smallest.user_key(),
|
||||
file->largest.user_key())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,49 +571,6 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Compaction::KeyRangeNotExistsBeyondOutputLevel(
|
||||
const Slice& begin_key, const Slice& end_key,
|
||||
std::vector<size_t>* level_ptrs) const {
|
||||
assert(input_version_ != nullptr);
|
||||
assert(level_ptrs != nullptr);
|
||||
assert(level_ptrs->size() == static_cast<size_t>(number_levels_));
|
||||
assert(cfd_->user_comparator()->CompareWithoutTimestamp(begin_key, end_key) <
|
||||
0);
|
||||
if (bottommost_level_) {
|
||||
return true /* does not overlap */;
|
||||
} else if (output_level_ != 0 &&
|
||||
cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
const Comparator* user_cmp = cfd_->user_comparator();
|
||||
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
|
||||
const std::vector<FileMetaData*>& files =
|
||||
input_vstorage_->LevelFiles(lvl);
|
||||
for (; level_ptrs->at(lvl) < files.size(); level_ptrs->at(lvl)++) {
|
||||
auto* f = files[level_ptrs->at(lvl)];
|
||||
// Advance until the first file with begin_key <= f->largest.user_key()
|
||||
if (user_cmp->CompareWithoutTimestamp(begin_key,
|
||||
f->largest.user_key()) > 0) {
|
||||
continue;
|
||||
}
|
||||
// We know that the previous file prev_f, if exists, has
|
||||
// prev_f->largest.user_key() < begin_key.
|
||||
if (user_cmp->CompareWithoutTimestamp(end_key,
|
||||
f->smallest.user_key()) <= 0) {
|
||||
// not overlapping with this level
|
||||
break;
|
||||
} else {
|
||||
// We have:
|
||||
// - begin_key < end_key,
|
||||
// - begin_key <= f->largest.user_key(), and
|
||||
// - end_key > f->smallest.user_key()
|
||||
return false /* overlap */;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true /* does not overlap */;
|
||||
}
|
||||
return false /* overlaps */;
|
||||
};
|
||||
|
||||
// Mark (or clear) each file that is being compacted
|
||||
void Compaction::MarkFilesBeingCompacted(bool mark_as_compacted) {
|
||||
for (size_t i = 0; i < num_input_levels(); i++) {
|
||||
@@ -869,16 +807,6 @@ uint64_t Compaction::MinInputFileOldestAncesterTime(
|
||||
return min_oldest_ancester_time;
|
||||
}
|
||||
|
||||
uint64_t Compaction::MinInputFileEpochNumber() const {
|
||||
uint64_t min_epoch_number = std::numeric_limits<uint64_t>::max();
|
||||
for (const auto& inputs_per_level : inputs_) {
|
||||
for (const auto& file : inputs_per_level.files) {
|
||||
min_epoch_number = std::min(min_epoch_number, file->epoch_number);
|
||||
}
|
||||
}
|
||||
return min_epoch_number;
|
||||
}
|
||||
|
||||
int Compaction::EvaluatePenultimateLevel(
|
||||
const VersionStorageInfo* vstorage,
|
||||
const ImmutableOptions& immutable_options, const int start_level,
|
||||
|
||||
+14
-44
@@ -31,19 +31,8 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// that key never appears in the database. We don't want adjacent sstables to
|
||||
// be considered overlapping if they are separated by the range tombstone
|
||||
// sentinel.
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const Slice&, const Slice&);
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const Slice& a,
|
||||
const InternalKey& b) {
|
||||
return sstableKeyCompare(user_cmp, a, b.Encode());
|
||||
}
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const Slice& b) {
|
||||
return sstableKeyCompare(user_cmp, a.Encode(), b);
|
||||
}
|
||||
inline int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b) {
|
||||
return sstableKeyCompare(user_cmp, a.Encode(), b.Encode());
|
||||
}
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
const InternalKey& b);
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey* a,
|
||||
const InternalKey& b);
|
||||
int sstableKeyCompare(const Comparator* user_cmp, const InternalKey& a,
|
||||
@@ -214,18 +203,10 @@ class Compaction {
|
||||
void AddInputDeletions(VersionEdit* edit);
|
||||
|
||||
// Returns true if the available information we have guarantees that
|
||||
// the input "user_key" does not exist in any level beyond `output_level()`.
|
||||
// the input "user_key" does not exist in any level beyond "output_level()".
|
||||
bool KeyNotExistsBeyondOutputLevel(const Slice& user_key,
|
||||
std::vector<size_t>* level_ptrs) const;
|
||||
|
||||
// Returns true if the user key range [begin_key, end_key) does not exist
|
||||
// in any level beyond `output_level()`.
|
||||
// Used for checking range tombstones, so we assume begin_key < end_key.
|
||||
// begin_key and end_key should include timestamp if enabled.
|
||||
bool KeyRangeNotExistsBeyondOutputLevel(
|
||||
const Slice& begin_key, const Slice& end_key,
|
||||
std::vector<size_t>* level_ptrs) const;
|
||||
|
||||
// Clear all files to indicate that they are not being compacted
|
||||
// Delete this compaction from the list of running compactions.
|
||||
//
|
||||
@@ -326,16 +307,12 @@ class Compaction {
|
||||
int output_level, VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs);
|
||||
|
||||
// If called before a compaction finishes, will return
|
||||
// table properties of all compaction input files.
|
||||
// If called after a compaction finished, will return
|
||||
// table properties of all compaction input and output files.
|
||||
const TablePropertiesCollection& GetTableProperties();
|
||||
TablePropertiesCollection GetOutputTableProperties() const {
|
||||
return output_table_properties_;
|
||||
}
|
||||
|
||||
void SetOutputTableProperties(
|
||||
const std::string& file_name,
|
||||
const std::shared_ptr<const TableProperties>& tp) {
|
||||
table_properties_[file_name] = tp;
|
||||
void SetOutputTableProperties(TablePropertiesCollection tp) {
|
||||
output_table_properties_ = std::move(tp);
|
||||
}
|
||||
|
||||
Slice GetSmallestUserKey() const { return smallest_user_key_; }
|
||||
@@ -401,9 +378,6 @@ class Compaction {
|
||||
// This is used to filter out some input files' ancester's time range.
|
||||
uint64_t MinInputFileOldestAncesterTime(const InternalKey* start,
|
||||
const InternalKey* end) const;
|
||||
// Return the minimum epoch number among
|
||||
// input files' associated with this compaction
|
||||
uint64_t MinInputFileEpochNumber() const;
|
||||
|
||||
// Called by DBImpl::NotifyOnCompactionCompleted to make sure number of
|
||||
// compaction begin and compaction completion callbacks match.
|
||||
@@ -522,9 +496,8 @@ class Compaction {
|
||||
// Does input compression match the output compression?
|
||||
bool InputCompressionMatchesOutput() const;
|
||||
|
||||
bool input_table_properties_initialized_ = false;
|
||||
// table properties of output files
|
||||
TablePropertiesCollection table_properties_;
|
||||
TablePropertiesCollection output_table_properties_;
|
||||
|
||||
// smallest user keys in compaction
|
||||
// includes timestamp if user-defined timestamp is enabled.
|
||||
@@ -570,16 +543,13 @@ struct PerKeyPlacementContext {
|
||||
const Slice value;
|
||||
const SequenceNumber seq_num;
|
||||
|
||||
bool& output_to_penultimate_level;
|
||||
bool output_to_penultimate_level;
|
||||
|
||||
PerKeyPlacementContext(int _level, Slice _key, Slice _value,
|
||||
SequenceNumber _seq_num,
|
||||
bool& _output_to_penultimate_level)
|
||||
: level(_level),
|
||||
key(_key),
|
||||
value(_value),
|
||||
seq_num(_seq_num),
|
||||
output_to_penultimate_level(_output_to_penultimate_level) {}
|
||||
SequenceNumber _seq_num)
|
||||
: level(_level), key(_key), value(_value), seq_num(_seq_num) {
|
||||
output_to_penultimate_level = false;
|
||||
}
|
||||
};
|
||||
#endif /* !NDEBUG */
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/prefetch_buffer_collection.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/wide/wide_column_serialization.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/listener.h"
|
||||
@@ -31,8 +30,7 @@ CompactionIterator::CompactionIterator(
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
bool must_count_input_entries, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low,
|
||||
@@ -46,9 +44,8 @@ CompactionIterator::CompactionIterator(
|
||||
manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new RealCompaction(compaction) : nullptr),
|
||||
must_count_input_entries, compaction_filter, shutting_down, info_log,
|
||||
full_history_ts_low, preserve_time_min_seqno,
|
||||
preclude_last_level_min_seqno) {}
|
||||
compaction_filter, shutting_down, info_log, full_history_ts_low,
|
||||
preserve_time_min_seqno, preclude_last_level_min_seqno) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -60,14 +57,15 @@ CompactionIterator::CompactionIterator(
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy> compaction, bool must_count_input_entries,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low,
|
||||
const SequenceNumber preserve_time_min_seqno,
|
||||
const SequenceNumber preclude_last_level_min_seqno)
|
||||
: input_(input, cmp, must_count_input_entries),
|
||||
: input_(input, cmp,
|
||||
!compaction || compaction->DoesInputReferenceBlobFiles()),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
snapshots_(snapshots),
|
||||
@@ -126,9 +124,6 @@ CompactionIterator::CompactionIterator(
|
||||
timestamp_size_ == full_history_ts_low_->size());
|
||||
#endif
|
||||
input_.SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
// The default `merge_until_status_` does not need to be checked since it is
|
||||
// overwritten as soon as `MergeUntil()` is called
|
||||
merge_until_status_.PermitUncheckedError();
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:AfterInit", compaction_.get());
|
||||
}
|
||||
|
||||
@@ -173,30 +168,11 @@ void CompactionIterator::Next() {
|
||||
}
|
||||
|
||||
// Keep current_key_ in sync.
|
||||
if (0 == timestamp_size_) {
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
} else {
|
||||
Slice ts = ikey_.GetTimestamp(timestamp_size_);
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type, &ts);
|
||||
}
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetInternalKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
validity_info_.SetValid(ValidContext::kMerge1);
|
||||
} else {
|
||||
if (merge_until_status_.IsMergeInProgress()) {
|
||||
// `Status::MergeInProgress()` tells us that the previous `MergeUntil()`
|
||||
// produced only merge operands. Those merge operands were accessed and
|
||||
// written out using `merge_out_iter_`. Since `merge_out_iter_` is
|
||||
// exhausted at this point, all merge operands have been written out.
|
||||
//
|
||||
// Still, there may be a base value (PUT, DELETE, SINGLEDEL, etc.) that
|
||||
// needs to be written out. Normally, `CompactionIterator` would skip it
|
||||
// on the basis that it has already output something in the same
|
||||
// snapshot stripe. To prevent this, we reset `has_current_user_key_` to
|
||||
// trick the future iteration from finding out the snapshot stripe is
|
||||
// unchanged.
|
||||
has_current_user_key_ = false;
|
||||
}
|
||||
// We consumed all pinned merge operands, release pinned iterators
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
// MergeHelper moves the iterator to the first record after the merged
|
||||
@@ -223,47 +199,39 @@ void CompactionIterator::Next() {
|
||||
|
||||
bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
Slice* skip_until) {
|
||||
if (!compaction_filter_) {
|
||||
// TODO: support compaction filter for wide-column entities
|
||||
if (!compaction_filter_ ||
|
||||
(ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex &&
|
||||
ikey_.type != kTypeWideColumnEntity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
CompactionFilter::Decision decision =
|
||||
CompactionFilter::Decision::kUndetermined;
|
||||
bool error = false;
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
// replace the entry with a deletion marker.
|
||||
CompactionFilter::Decision filter = CompactionFilter::Decision::kUndetermined;
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
CompactionFilter::ValueType value_type =
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
: ikey_.type == kTypeBlobIndex
|
||||
? CompactionFilter::ValueType::kBlobIndex
|
||||
: CompactionFilter::ValueType::kWideColumnEntity;
|
||||
|
||||
: CompactionFilter::ValueType::kBlobIndex;
|
||||
// Hack: pass internal key to BlobIndexCompactionFilter since it needs
|
||||
// to get sequence number.
|
||||
assert(compaction_filter_);
|
||||
const Slice& filter_key =
|
||||
(ikey_.type != kTypeBlobIndex ||
|
||||
Slice& filter_key =
|
||||
(ikey_.type == kTypeValue ||
|
||||
!compaction_filter_->IsStackedBlobDbInternalCompactionFilter())
|
||||
? ikey_.user_key
|
||||
: key_;
|
||||
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> new_columns;
|
||||
|
||||
{
|
||||
StopWatchNano timer(clock_, report_detailed_time_);
|
||||
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
decision = compaction_filter_->FilterBlobByKey(
|
||||
if (kTypeBlobIndex == ikey_.type) {
|
||||
filter = compaction_filter_->FilterBlobByKey(
|
||||
level_, filter_key, &compaction_filter_value_,
|
||||
compaction_filter_skip_until_.rep());
|
||||
if (decision == CompactionFilter::Decision::kUndetermined &&
|
||||
if (CompactionFilter::Decision::kUndetermined == filter &&
|
||||
!compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
if (!compaction_) {
|
||||
if (compaction_ == nullptr) {
|
||||
status_ =
|
||||
Status::Corruption("Unexpected blob index outside of compaction");
|
||||
validity_info_.Invalidate();
|
||||
@@ -309,61 +277,33 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
value_type = CompactionFilter::ValueType::kValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (decision == CompactionFilter::Decision::kUndetermined) {
|
||||
const Slice* existing_val = nullptr;
|
||||
const WideColumns* existing_col = nullptr;
|
||||
|
||||
WideColumns existing_columns;
|
||||
|
||||
if (ikey_.type != kTypeWideColumnEntity) {
|
||||
if (!blob_value_.empty()) {
|
||||
existing_val = &blob_value_;
|
||||
} else {
|
||||
existing_val = &value_;
|
||||
}
|
||||
} else {
|
||||
Slice value_copy = value_;
|
||||
const Status s =
|
||||
WideColumnSerialization::Deserialize(value_copy, existing_columns);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
existing_col = &existing_columns;
|
||||
}
|
||||
|
||||
decision = compaction_filter_->FilterV3(
|
||||
level_, filter_key, value_type, existing_val, existing_col,
|
||||
&compaction_filter_value_, &new_columns,
|
||||
if (CompactionFilter::Decision::kUndetermined == filter) {
|
||||
filter = compaction_filter_->FilterV2(
|
||||
level_, filter_key, value_type,
|
||||
blob_value_.empty() ? value_ : blob_value_, &compaction_filter_value_,
|
||||
compaction_filter_skip_until_.rep());
|
||||
}
|
||||
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr && report_detailed_time_ ? timer.ElapsedNanos() : 0;
|
||||
}
|
||||
|
||||
if (decision == CompactionFilter::Decision::kUndetermined) {
|
||||
// Should not reach here, since FilterV2/FilterV3 should never return
|
||||
// kUndetermined.
|
||||
status_ = Status::NotSupported(
|
||||
"FilterV2/FilterV3 should never return kUndetermined");
|
||||
if (CompactionFilter::Decision::kUndetermined == filter) {
|
||||
// Should not reach here, since FilterV2 should never return kUndetermined.
|
||||
status_ =
|
||||
Status::NotSupported("FilterV2() should never return kUndetermined");
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (decision == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
|
||||
0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2/FilterV3 documentation.
|
||||
decision = CompactionFilter::Decision::kKeep;
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
filter = CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
if (decision == CompactionFilter::Decision::kRemove) {
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
@@ -371,7 +311,7 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
// no value associated with delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (decision == CompactionFilter::Decision::kPurge) {
|
||||
} else if (filter == CompactionFilter::Decision::kPurge) {
|
||||
// convert the current key to a single delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeSingleDeletion;
|
||||
@@ -379,19 +319,19 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
// no value associated with single delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (decision == CompactionFilter::Decision::kChangeValue) {
|
||||
if (ikey_.type != kTypeValue) {
|
||||
} else if (filter == CompactionFilter::Decision::kChangeValue) {
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
// value transfer from blob file to inlined data
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeValue);
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (decision == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
*need_skip = true;
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
*skip_until = compaction_filter_skip_until_.Encode();
|
||||
} else if (decision == CompactionFilter::Decision::kChangeBlobIndex) {
|
||||
} else if (filter == CompactionFilter::Decision::kChangeBlobIndex) {
|
||||
// Only the StackableDB-based BlobDB impl's compaction filter should return
|
||||
// kChangeBlobIndex. Decision about rewriting blob and changing blob index
|
||||
// in the integrated BlobDB impl is made in subsequent call to
|
||||
@@ -403,56 +343,23 @@ bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ikey_.type != kTypeBlobIndex) {
|
||||
if (ikey_.type == kTypeValue) {
|
||||
// value transfer from inlined data to blob file
|
||||
ikey_.type = kTypeBlobIndex;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeBlobIndex);
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (decision == CompactionFilter::Decision::kIOError) {
|
||||
} else if (filter == CompactionFilter::Decision::kIOError) {
|
||||
if (!compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
status_ = Status::NotSupported(
|
||||
"CompactionFilter for integrated BlobDB should not return kIOError");
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
status_ = Status::IOError("Failed to access blob during compaction filter");
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
} else if (decision == CompactionFilter::Decision::kChangeWideColumnEntity) {
|
||||
WideColumns sorted_columns;
|
||||
|
||||
sorted_columns.reserve(new_columns.size());
|
||||
for (const auto& column : new_columns) {
|
||||
sorted_columns.emplace_back(column.first, column.second);
|
||||
}
|
||||
|
||||
std::sort(sorted_columns.begin(), sorted_columns.end(),
|
||||
[](const WideColumn& lhs, const WideColumn& rhs) {
|
||||
return lhs.name().compare(rhs.name()) < 0;
|
||||
});
|
||||
|
||||
{
|
||||
const Status s = WideColumnSerialization::Serialize(
|
||||
sorted_columns, compaction_filter_value_);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
validity_info_.Invalidate();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ikey_.type != kTypeWideColumnEntity) {
|
||||
ikey_.type = kTypeWideColumnEntity;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeWideColumnEntity);
|
||||
}
|
||||
|
||||
value_ = compaction_filter_value_;
|
||||
error = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
return !error;
|
||||
}
|
||||
|
||||
void CompactionIterator::NextFromInput() {
|
||||
@@ -465,7 +372,6 @@ void CompactionIterator::NextFromInput() {
|
||||
value_ = input_.value();
|
||||
blob_value_.Reset();
|
||||
iter_stats_.num_input_records++;
|
||||
is_range_del_ = input_.IsDeleteRangeSentinelKey();
|
||||
|
||||
Status pik_status = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
if (!pik_status.ok()) {
|
||||
@@ -485,10 +391,7 @@ void CompactionIterator::NextFromInput() {
|
||||
break;
|
||||
}
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
|
||||
if (is_range_del_) {
|
||||
validity_info_.SetValid(kRangeDeletion);
|
||||
break;
|
||||
}
|
||||
|
||||
// Update input statistics
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion ||
|
||||
ikey_.type == kTypeDeletionWithTimestamp) {
|
||||
@@ -710,14 +613,6 @@ void CompactionIterator::NextFromInput() {
|
||||
|
||||
ParsedInternalKey next_ikey;
|
||||
AdvanceInputIter();
|
||||
while (input_.Valid() && input_.IsDeleteRangeSentinelKey() &&
|
||||
ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok() &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
// skip range tombstone start keys with the same user key
|
||||
// since they are not "real" point keys.
|
||||
AdvanceInputIter();
|
||||
}
|
||||
|
||||
// Check whether the next key exists, is not corrupt, and is the same key
|
||||
// as the single delete.
|
||||
@@ -725,7 +620,6 @@ void CompactionIterator::NextFromInput() {
|
||||
ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok() &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
assert(!input_.IsDeleteRangeSentinelKey());
|
||||
#ifndef NDEBUG
|
||||
const Compaction* c =
|
||||
compaction_ ? compaction_->real_compaction() : nullptr;
|
||||
@@ -931,8 +825,8 @@ void CompactionIterator::NextFromInput() {
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
bottommost_level_) {
|
||||
// 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
|
||||
// We can skip outputting the key iff there are no subsequent puts for this
|
||||
// key
|
||||
assert(!compaction_ || compaction_->KeyNotExistsBeyondOutputLevel(
|
||||
ikey_.user_key, &level_ptrs_));
|
||||
ParsedInternalKey next_ikey;
|
||||
@@ -950,19 +844,17 @@ void CompactionIterator::NextFromInput() {
|
||||
// Note that a deletion marker of type kTypeDeletionWithTimestamp will be
|
||||
// considered to have a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
//
|
||||
// Range tombstone start keys are skipped as they are not "real" keys.
|
||||
while (!IsPausingManualCompaction() && !IsShuttingDown() &&
|
||||
input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 || input_.IsDeleteRangeSentinelKey() ||
|
||||
(prev_snapshot == 0 ||
|
||||
DefinitelyNotInSnapshot(next_ikey.sequence, prev_snapshot))) {
|
||||
AdvanceInputIter();
|
||||
}
|
||||
// If you find you still need to output a row with this key, we need to
|
||||
// output the delete too
|
||||
// If you find you still need to output a row with this key, we need to output the
|
||||
// delete too
|
||||
if (input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
@@ -983,15 +875,14 @@ void CompactionIterator::NextFromInput() {
|
||||
// have hit (A)
|
||||
// We encapsulate the merge related state machine in a different
|
||||
// object to minimize change to the existing flow.
|
||||
merge_until_status_ = merge_helper_->MergeUntil(
|
||||
Status s = merge_helper_->MergeUntil(
|
||||
&input_, range_del_agg_, prev_snapshot, bottommost_level_,
|
||||
allow_data_in_errors_, blob_fetcher_.get(), full_history_ts_low_,
|
||||
prefetch_buffers_.get(), &iter_stats_);
|
||||
allow_data_in_errors_, blob_fetcher_.get(), prefetch_buffers_.get(),
|
||||
&iter_stats_);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
|
||||
if (!merge_until_status_.ok() &&
|
||||
!merge_until_status_.IsMergeInProgress()) {
|
||||
status_ = merge_until_status_;
|
||||
if (!s.ok() && !s.IsMergeInProgress()) {
|
||||
status_ = s;
|
||||
return;
|
||||
} else if (merge_out_iter_.Valid()) {
|
||||
// NOTE: key, value, and ikey_ refer to old entries.
|
||||
@@ -1202,7 +1093,17 @@ void CompactionIterator::GarbageCollectBlobIfNeeded() {
|
||||
|
||||
void CompactionIterator::DecideOutputLevel() {
|
||||
assert(compaction_->SupportsPerKeyPlacement());
|
||||
#ifndef NDEBUG
|
||||
// Could be overridden by unittest
|
||||
PerKeyPlacementContext context(level_, ikey_.user_key, value_,
|
||||
ikey_.sequence);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
|
||||
&context);
|
||||
output_to_penultimate_level_ = context.output_to_penultimate_level;
|
||||
#else
|
||||
output_to_penultimate_level_ = false;
|
||||
#endif // NDEBUG
|
||||
|
||||
// if the key is newer than the cutoff sequence or within the earliest
|
||||
// snapshot, it should output to the penultimate level.
|
||||
if (ikey_.sequence > preclude_last_level_min_seqno_ ||
|
||||
@@ -1210,17 +1111,6 @@ void CompactionIterator::DecideOutputLevel() {
|
||||
output_to_penultimate_level_ = true;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Could be overridden by unittest
|
||||
PerKeyPlacementContext context(level_, ikey_.user_key, value_, ikey_.sequence,
|
||||
output_to_penultimate_level_);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context",
|
||||
&context);
|
||||
if (ikey_.sequence > earliest_snapshot_) {
|
||||
output_to_penultimate_level_ = true;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
|
||||
if (output_to_penultimate_level_) {
|
||||
// If it's decided to output to the penultimate level, but unsafe to do so,
|
||||
// still output to the last level. For example, moving the data from a lower
|
||||
@@ -1252,12 +1142,10 @@ void CompactionIterator::DecideOutputLevel() {
|
||||
|
||||
void CompactionIterator::PrepareOutput() {
|
||||
if (Valid()) {
|
||||
if (LIKELY(!is_range_del_)) {
|
||||
if (ikey_.type == kTypeValue) {
|
||||
ExtractLargeValueIfNeeded();
|
||||
} else if (ikey_.type == kTypeBlobIndex) {
|
||||
GarbageCollectBlobIfNeeded();
|
||||
}
|
||||
if (ikey_.type == kTypeValue) {
|
||||
ExtractLargeValueIfNeeded();
|
||||
} else if (ikey_.type == kTypeBlobIndex) {
|
||||
GarbageCollectBlobIfNeeded();
|
||||
}
|
||||
|
||||
if (compaction_ != nullptr && compaction_->SupportsPerKeyPlacement()) {
|
||||
@@ -1280,7 +1168,7 @@ void CompactionIterator::PrepareOutput() {
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
ikey_.type != kTypeMerge && current_key_committed_ &&
|
||||
!output_to_penultimate_level_ &&
|
||||
ikey_.sequence < preserve_time_min_seqno_ && !is_range_del_) {
|
||||
ikey_.sequence < preserve_time_min_seqno_) {
|
||||
if (ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeSingleDeletion && timestamp_size_ == 0)) {
|
||||
ROCKS_LOG_FATAL(
|
||||
@@ -1326,8 +1214,8 @@ inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"No snapshot left in findEarliestVisibleSnapshot");
|
||||
}
|
||||
auto snapshots_iter =
|
||||
std::lower_bound(snapshots_->begin(), snapshots_->end(), in);
|
||||
auto snapshots_iter = std::lower_bound(
|
||||
snapshots_->begin(), snapshots_->end(), in);
|
||||
assert(prev_snapshot != nullptr);
|
||||
if (snapshots_iter == snapshots_->begin()) {
|
||||
*prev_snapshot = 0;
|
||||
@@ -1342,8 +1230,8 @@ inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
}
|
||||
}
|
||||
if (snapshot_checker_ == nullptr) {
|
||||
return snapshots_iter != snapshots_->end() ? *snapshots_iter
|
||||
: kMaxSequenceNumber;
|
||||
return snapshots_iter != snapshots_->end()
|
||||
? *snapshots_iter : kMaxSequenceNumber;
|
||||
}
|
||||
bool has_released_snapshot = !released_snapshots_.empty();
|
||||
for (; snapshots_iter != snapshots_->end(); ++snapshots_iter) {
|
||||
@@ -1413,7 +1301,6 @@ std::unique_ptr<BlobFetcher> CompactionIterator::CreateBlobFetcherIfNeeded(
|
||||
}
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.io_activity = Env::IOActivity::kCompaction;
|
||||
read_options.fill_cache = false;
|
||||
|
||||
return std::unique_ptr<BlobFetcher>(new BlobFetcher(version, read_options));
|
||||
|
||||
@@ -38,18 +38,15 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
bool Valid() const override { return inner_iter_->Valid(); }
|
||||
Status status() const override { return inner_iter_->status(); }
|
||||
void Next() override {
|
||||
if (!inner_iter_->IsDeleteRangeSentinelKey()) {
|
||||
num_itered_++;
|
||||
}
|
||||
num_itered_++;
|
||||
inner_iter_->Next();
|
||||
}
|
||||
void Seek(const Slice& target) override {
|
||||
if (!need_count_entries_) {
|
||||
has_num_itered_ = false;
|
||||
inner_iter_->Seek(target);
|
||||
} else {
|
||||
// Need to count total number of entries,
|
||||
// so we do Next() rather than Seek().
|
||||
// For flush cases, we need to count total number of entries, so we
|
||||
// do Next() rather than Seek().
|
||||
while (inner_iter_->Valid() &&
|
||||
icmp_.Compare(inner_iter_->key(), target) < 0) {
|
||||
Next();
|
||||
@@ -65,19 +62,13 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
void SeekForPrev(const Slice& /* target */) override { assert(false); }
|
||||
void SeekToLast() override { assert(false); }
|
||||
|
||||
uint64_t NumItered() const { return num_itered_; }
|
||||
bool HasNumItered() const { return has_num_itered_; }
|
||||
bool IsDeleteRangeSentinelKey() const override {
|
||||
assert(Valid());
|
||||
return inner_iter_->IsDeleteRangeSentinelKey();
|
||||
}
|
||||
uint64_t num_itered() const { return num_itered_; }
|
||||
|
||||
private:
|
||||
InternalKeyComparator icmp_;
|
||||
InternalIterator* inner_iter_; // not owned
|
||||
uint64_t num_itered_ = 0;
|
||||
bool need_count_entries_;
|
||||
bool has_num_itered_ = true;
|
||||
};
|
||||
|
||||
class CompactionIterator {
|
||||
@@ -194,10 +185,6 @@ 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.
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
@@ -208,7 +195,7 @@ class CompactionIterator {
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
bool must_count_input_entries, const Compaction* compaction = nullptr,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
@@ -228,7 +215,6 @@ class CompactionIterator {
|
||||
bool enforce_single_del_contracts,
|
||||
const std::atomic<bool>& manual_compaction_canceled,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
bool must_count_input_entries,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
@@ -256,15 +242,9 @@ class CompactionIterator {
|
||||
const Status& status() const { return status_; }
|
||||
const ParsedInternalKey& ikey() const { return ikey_; }
|
||||
inline bool Valid() const { return validity_info_.IsValid(); }
|
||||
const Slice& user_key() const {
|
||||
if (UNLIKELY(is_range_del_)) {
|
||||
return ikey_.user_key;
|
||||
}
|
||||
return current_user_key_;
|
||||
}
|
||||
const Slice& user_key() const { return current_user_key_; }
|
||||
const CompactionIterationStats& iter_stats() const { return iter_stats_; }
|
||||
bool HasNumInputEntryScanned() const { return input_.HasNumItered(); }
|
||||
uint64_t NumInputEntryScanned() const { return input_.NumItered(); }
|
||||
uint64_t num_input_entry_scanned() const { return input_.num_itered(); }
|
||||
// If the current key should be placed on penultimate level, only valid if
|
||||
// per_key_placement is supported
|
||||
bool output_to_penultimate_level() const {
|
||||
@@ -272,8 +252,6 @@ class CompactionIterator {
|
||||
}
|
||||
Status InputStatus() const { return input_.status(); }
|
||||
|
||||
bool IsDeleteRangeSentinelKey() const { return is_range_del_; }
|
||||
|
||||
private:
|
||||
// Processes the input stream to find the next output
|
||||
void NextFromInput();
|
||||
@@ -407,7 +385,6 @@ class CompactionIterator {
|
||||
kKeepSD = 8,
|
||||
kKeepDel = 9,
|
||||
kNewUserKey = 10,
|
||||
kRangeDeletion = 11,
|
||||
};
|
||||
|
||||
struct ValidityInfo {
|
||||
@@ -455,7 +432,6 @@ class CompactionIterator {
|
||||
bool clear_and_output_next_key_ = false;
|
||||
|
||||
MergeOutputIterator merge_out_iter_;
|
||||
Status merge_until_status_;
|
||||
// PinnedIteratorsManager used to pin input_ Iterator blocks while reading
|
||||
// merge operands and then releasing them after consuming them.
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
@@ -516,10 +492,6 @@ class CompactionIterator {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
return manual_compaction_canceled_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Stores whether the current compaction iterator output
|
||||
// is a range tombstone start key.
|
||||
bool is_range_del_{false};
|
||||
};
|
||||
|
||||
inline bool CompactionIterator::DefinitelyInSnapshot(SequenceNumber seq,
|
||||
|
||||
@@ -203,8 +203,7 @@ class TestSnapshotChecker : public SnapshotChecker {
|
||||
public:
|
||||
explicit TestSnapshotChecker(
|
||||
SequenceNumber last_committed_sequence,
|
||||
const std::unordered_map<SequenceNumber, SequenceNumber>& snapshots =
|
||||
{{}})
|
||||
const std::unordered_map<SequenceNumber, SequenceNumber>& snapshots = {{}})
|
||||
: last_committed_sequence_(last_committed_sequence),
|
||||
snapshots_(snapshots) {}
|
||||
|
||||
@@ -293,8 +292,8 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
nullptr /* blob_file_builder */, true /*allow_data_in_errors*/,
|
||||
true /*enforce_single_del_contracts*/,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse_,
|
||||
std::move(compaction), /*must_count_input_entries=*/false, filter,
|
||||
&shutting_down_, /*info_log=*/nullptr, full_history_ts_low));
|
||||
std::move(compaction), filter, &shutting_down_, /*info_log=*/nullptr,
|
||||
full_history_ts_low));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
@@ -812,8 +811,6 @@ TEST_P(PerKeyPlacementCompIteratorTest, SplitLastLevelData) {
|
||||
c_iter_->Next();
|
||||
ASSERT_OK(c_iter_->status());
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(PerKeyPlacementCompIteratorTest, SnapshotData) {
|
||||
@@ -879,8 +876,6 @@ TEST_P(PerKeyPlacementCompIteratorTest, ConflictWithSnapshot) {
|
||||
// output_to_penultimate_level.
|
||||
c_iter_->Next();
|
||||
ASSERT_TRUE(c_iter_->status().IsCorruption());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(PerKeyPlacementCompIteratorTest,
|
||||
@@ -1043,7 +1038,7 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveDeletionIfValuePresentToEarlierSnapshot) {
|
||||
AddSnapshot(2, 1);
|
||||
AddSnapshot(2,1);
|
||||
RunTest({test::KeyStr("a", 4, kTypeDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""},
|
||||
@@ -1255,31 +1250,6 @@ TEST_P(CompactionIteratorTsGcTest, NoKeyEligibleForGC) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, NoMergeEligibleForGc) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(10002, user_key, 102, kTypeMerge),
|
||||
test::KeyStr(10001, user_key, 101, kTypeMerge),
|
||||
test::KeyStr(10000, user_key, 100, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"2", "1", "a0"};
|
||||
std::shared_ptr<MergeOperator> merge_op =
|
||||
MergeOperators::CreateStringAppendTESTOperator();
|
||||
const std::vector<std::string>& expected_keys = input_keys;
|
||||
const std::vector<std::string>& expected_values = input_values;
|
||||
const std::vector<std::pair<bool, bool>> params = {
|
||||
{false, false}, {false, true}, {true, true}};
|
||||
for (const auto& param : params) {
|
||||
const bool bottommost_level = param.first;
|
||||
const bool key_not_exists_beyond_output_level = param.second;
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber, merge_op.get(),
|
||||
/*compaction_filter=*/nullptr, bottommost_level,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
key_not_exists_beyond_output_level,
|
||||
/*full_history_ts_low=*/nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, AllKeysOlderThanThreshold) {
|
||||
constexpr char user_key[][2] = {{'a', '\0'}, {'b', '\0'}};
|
||||
const std::vector<std::string> input_keys = {
|
||||
@@ -1333,91 +1303,6 @@ TEST_P(CompactionIteratorTsGcTest, AllKeysOlderThanThreshold) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, SomeMergesOlderThanThreshold) {
|
||||
constexpr char user_key[][2] = {"a", "f"};
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/25000, user_key[0], /*seq=*/2500, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/19000, user_key[0], /*seq=*/2300, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/18000, user_key[0], /*seq=*/1800, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/16000, user_key[0], /*seq=*/1600, kTypeValue),
|
||||
test::KeyStr(/*ts=*/19000, user_key[1], /*seq=*/2000, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/17000, user_key[1], /*seq=*/1700, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/15000, user_key[1], /*seq=*/1600,
|
||||
kTypeDeletionWithTimestamp)};
|
||||
const std::vector<std::string> input_values = {"25", "19", "18", "16",
|
||||
"19", "17", ""};
|
||||
std::shared_ptr<MergeOperator> merge_op =
|
||||
MergeOperators::CreateStringAppendTESTOperator();
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 20000);
|
||||
|
||||
const std::vector<std::pair<bool, bool>> params = {
|
||||
{false, false}, {false, true}, {true, true}};
|
||||
|
||||
{
|
||||
AddSnapshot(1600);
|
||||
AddSnapshot(1900);
|
||||
const std::vector<std::string> expected_keys = {
|
||||
test::KeyStr(/*ts=*/25000, user_key[0], /*seq=*/2500, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/19000, user_key[0], /*seq=*/2300, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/18000, user_key[0], /*seq=*/1800, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/16000, user_key[0], /*seq=*/1600, kTypeValue),
|
||||
test::KeyStr(/*ts=*/19000, user_key[1], /*seq=*/2000, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/17000, user_key[1], /*seq=*/1700, kTypeMerge),
|
||||
test::KeyStr(/*ts=*/15000, user_key[1], /*seq=*/1600,
|
||||
kTypeDeletionWithTimestamp)};
|
||||
const std::vector<std::string> expected_values = {"25", "19", "18", "16",
|
||||
"19", "17", ""};
|
||||
for (const auto& param : params) {
|
||||
const bool bottommost_level = param.first;
|
||||
const bool key_not_exists_beyond_output_level = param.second;
|
||||
auto expected_keys_copy = expected_keys;
|
||||
auto expected_values_copy = expected_values;
|
||||
if (bottommost_level || key_not_exists_beyond_output_level) {
|
||||
// the kTypeDeletionWithTimestamp will be dropped
|
||||
expected_keys_copy.pop_back();
|
||||
expected_values_copy.pop_back();
|
||||
if (bottommost_level) {
|
||||
// seq zero
|
||||
expected_keys_copy[3] =
|
||||
test::KeyStr(/*ts=*/0, user_key[0], /*seq=*/0, kTypeValue);
|
||||
}
|
||||
}
|
||||
RunTest(input_keys, input_values, expected_keys_copy,
|
||||
expected_values_copy,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber, merge_op.get(),
|
||||
/*compaction_filter=*/nullptr, bottommost_level,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
key_not_exists_beyond_output_level, &full_history_ts_low);
|
||||
}
|
||||
ClearSnapshots();
|
||||
}
|
||||
|
||||
// No snapshots
|
||||
{
|
||||
const std::vector<std::string> expected_keys = {
|
||||
test::KeyStr(/*ts=*/25000, user_key[0], /*seq=*/2500, kTypeValue),
|
||||
test::KeyStr(/*ts=*/19000, user_key[1], /*seq=*/2000, kTypeValue)};
|
||||
const std::vector<std::string> expected_values = {"16,18,19,25", "17,19"};
|
||||
for (const auto& param : params) {
|
||||
const bool bottommost_level = param.first;
|
||||
const bool key_not_exists_beyond_output_level = param.second;
|
||||
auto expected_keys_copy = expected_keys;
|
||||
auto expected_values_copy = expected_values;
|
||||
if (bottommost_level) {
|
||||
expected_keys_copy[1] =
|
||||
test::KeyStr(/*ts=*/0, user_key[1], /*seq=*/0, kTypeValue);
|
||||
}
|
||||
RunTest(input_keys, input_values, expected_keys_copy,
|
||||
expected_values_copy,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber, merge_op.get(),
|
||||
/*compaction_filter=*/nullptr, bottommost_level,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
key_not_exists_beyond_output_level, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, NewHidesOldSameSnapshot) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
|
||||
+99
-158
@@ -99,8 +99,6 @@ const char* GetCompactionReasonString(CompactionReason compaction_reason) {
|
||||
return "ForcedBlobGC";
|
||||
case CompactionReason::kRoundRobinTtl:
|
||||
return "RoundRobinTtl";
|
||||
case CompactionReason::kRefitLevel:
|
||||
return "RefitLevel";
|
||||
case CompactionReason::kNumOfReasons:
|
||||
// fall through
|
||||
default:
|
||||
@@ -192,8 +190,8 @@ CompactionJob::CompactionJob(
|
||||
assert(log_buffer_ != nullptr);
|
||||
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetEnableTracking(db_options_.enable_thread_tracking);
|
||||
ThreadStatusUtil::SetColumnFamily(cfd);
|
||||
ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
|
||||
db_options_.enable_thread_tracking);
|
||||
ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION);
|
||||
ReportStartedCompaction(compaction);
|
||||
}
|
||||
@@ -204,6 +202,10 @@ CompactionJob::~CompactionJob() {
|
||||
}
|
||||
|
||||
void CompactionJob::ReportStartedCompaction(Compaction* compaction) {
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
|
||||
db_options_.enable_thread_tracking);
|
||||
|
||||
ThreadStatusUtil::SetThreadOperationProperty(ThreadStatus::COMPACTION_JOB_ID,
|
||||
job_id_);
|
||||
|
||||
@@ -260,7 +262,7 @@ void CompactionJob::Prepare() {
|
||||
StopWatch sw(db_options_.clock, stats_, SUBCOMPACTION_SETUP_TIME);
|
||||
GenSubcompactionBoundaries();
|
||||
}
|
||||
if (boundaries_.size() >= 1) {
|
||||
if (boundaries_.size() > 1) {
|
||||
for (size_t i = 0; i <= boundaries_.size(); i++) {
|
||||
compact_->sub_compact_states.emplace_back(
|
||||
c, (i != 0) ? std::optional<Slice>(boundaries_[i - 1]) : std::nullopt,
|
||||
@@ -287,14 +289,12 @@ void CompactionJob::Prepare() {
|
||||
c->immutable_options()->preclude_last_level_data_seconds);
|
||||
|
||||
if (preserve_time_duration > 0) {
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
// setup seqno_time_mapping_
|
||||
seqno_time_mapping_.SetMaxTimeDuration(preserve_time_duration);
|
||||
for (const auto& each_level : *c->inputs()) {
|
||||
for (const auto& fmd : each_level.files) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
Status s =
|
||||
cfd->current()->GetTableProperties(read_options, &tp, fmd, nullptr);
|
||||
Status s = cfd->current()->GetTableProperties(&tp, fmd, nullptr);
|
||||
if (s.ok()) {
|
||||
seqno_time_mapping_.Add(tp->seqno_to_time_mapping)
|
||||
.PermitUncheckedError();
|
||||
@@ -470,7 +470,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
// overlap with N-1 other ranges. Since we requested a relatively large number
|
||||
// (128) of ranges from each input files, even N range overlapping would
|
||||
// cause relatively small inaccuracy.
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
|
||||
auto* c = compact_->compaction;
|
||||
if (c->max_subcompactions() <= 1 &&
|
||||
!(c->immutable_options()->compaction_pri == kRoundRobin &&
|
||||
@@ -504,9 +504,7 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
FileMetaData* f = flevel->files[i].file_metadata;
|
||||
std::vector<TableReader::Anchor> my_anchors;
|
||||
Status s = cfd->table_cache()->ApproximateKeyAnchors(
|
||||
read_options, icomp, *f,
|
||||
c->mutable_cf_options()->block_protection_bytes_per_key,
|
||||
my_anchors);
|
||||
ReadOptions(), icomp, *f, my_anchors);
|
||||
if (!s.ok() || my_anchors.empty()) {
|
||||
my_anchors.emplace_back(f->largest.user_key(), f->fd.GetFileSize());
|
||||
}
|
||||
@@ -716,18 +714,16 @@ Status CompactionJob::Run() {
|
||||
break;
|
||||
}
|
||||
// Verify that the table is usable
|
||||
// We set for_compaction to false and don't
|
||||
// OptimizeForCompactionTableRead here because this is a special case
|
||||
// after we finish the table building No matter whether
|
||||
// use_direct_io_for_flush_and_compaction is true, we will regard this
|
||||
// verification as user reads since the goal is to cache it here for
|
||||
// further user reads
|
||||
const ReadOptions verify_table_read_options(
|
||||
Env::IOActivity::kCompaction);
|
||||
// We set for_compaction to false and don't OptimizeForCompactionTableRead
|
||||
// here because this is a special case after we finish the table building
|
||||
// No matter whether use_direct_io_for_flush_and_compaction is true,
|
||||
// we will regard this verification as user reads since the goal is
|
||||
// to cache it here for further user reads
|
||||
ReadOptions read_options;
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
verify_table_read_options, file_options_,
|
||||
cfd->internal_comparator(), files_output[file_idx]->meta,
|
||||
/*range_del_agg=*/nullptr, prefix_extractor,
|
||||
read_options, file_options_, cfd->internal_comparator(),
|
||||
files_output[file_idx]->meta, /*range_del_agg=*/nullptr,
|
||||
prefix_extractor,
|
||||
/*table_reader_ptr=*/nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
@@ -737,9 +733,7 @@ Status CompactionJob::Run() {
|
||||
*compact_->compaction->mutable_cf_options()),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key=*/nullptr,
|
||||
/*allow_unprepared_value=*/false,
|
||||
compact_->compaction->mutable_cf_options()
|
||||
->block_protection_bytes_per_key);
|
||||
/*allow_unprepared_value=*/false);
|
||||
auto s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
@@ -770,8 +764,8 @@ Status CompactionJob::Run() {
|
||||
}
|
||||
};
|
||||
for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) {
|
||||
thread_pool.emplace_back(
|
||||
verify_table, std::ref(compact_->sub_compact_states[i].status));
|
||||
thread_pool.emplace_back(verify_table,
|
||||
std::ref(compact_->sub_compact_states[i].status));
|
||||
}
|
||||
verify_table(compact_->sub_compact_states[0].status);
|
||||
for (auto& thread : thread_pool) {
|
||||
@@ -796,51 +790,20 @@ Status CompactionJob::Run() {
|
||||
auto fn =
|
||||
TableFileName(state.compaction->immutable_options()->cf_paths,
|
||||
output.meta.fd.GetNumber(), output.meta.fd.GetPathId());
|
||||
compact_->compaction->SetOutputTableProperties(fn,
|
||||
output.table_properties);
|
||||
tp[fn] = output.table_properties;
|
||||
}
|
||||
}
|
||||
compact_->compaction->SetOutputTableProperties(std::move(tp));
|
||||
|
||||
// Finish up all bookkeeping to unify the subcompaction results.
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
compact_->AggregateCompactionStats(compaction_stats_, *compaction_job_stats_);
|
||||
uint64_t num_input_range_del = 0;
|
||||
bool ok = UpdateCompactionStats(&num_input_range_del);
|
||||
// (Sub)compactions returned ok, do sanity check on the number of input keys.
|
||||
if (status.ok() && ok && compaction_job_stats_->has_num_input_records) {
|
||||
size_t ts_sz = compact_->compaction->column_family_data()
|
||||
->user_comparator()
|
||||
->timestamp_size();
|
||||
// When trim_ts_ is non-empty, CompactionIterator takes
|
||||
// HistoryTrimmingIterator as input iterator and sees a trimmed view of
|
||||
// input keys. So the number of keys it processed is not suitable for
|
||||
// verification here.
|
||||
// TODO: support verification when trim_ts_ is non-empty.
|
||||
if (!(ts_sz > 0 && !trim_ts_.empty()) &&
|
||||
db_options_.compaction_verify_record_count) {
|
||||
assert(compaction_stats_.stats.num_input_records > 0);
|
||||
// TODO: verify the number of range deletion entries.
|
||||
uint64_t expected =
|
||||
compaction_stats_.stats.num_input_records - num_input_range_del;
|
||||
uint64_t actual = compaction_job_stats_->num_input_records;
|
||||
if (expected != actual) {
|
||||
std::string msg =
|
||||
"Total number of input records: " + std::to_string(expected) +
|
||||
", but processed " + std::to_string(actual) + " records.";
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log, "[%s] [JOB %d] Compaction %s",
|
||||
compact_->compaction->column_family_data()->GetName().c_str(),
|
||||
job_context_->job_id, msg.c_str());
|
||||
status = Status::Corruption(
|
||||
"Compaction number of input keys does not match number of keys "
|
||||
"processed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateCompactionStats();
|
||||
|
||||
RecordCompactionIOStats();
|
||||
LogFlush(db_options_.info_log);
|
||||
TEST_SYNC_POINT("CompactionJob::Run():End");
|
||||
|
||||
compact_->status = status;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():EndStatusSet", &status);
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1012,6 +975,7 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
|
||||
void CompactionJob::NotifyOnSubcompactionBegin(
|
||||
SubcompactionState* sub_compact) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
Compaction* c = compact_->compaction;
|
||||
|
||||
if (db_options_.listeners.empty()) {
|
||||
@@ -1037,10 +1001,14 @@ void CompactionJob::NotifyOnSubcompactionBegin(
|
||||
}
|
||||
info.status.PermitUncheckedError();
|
||||
|
||||
#else
|
||||
(void)sub_compact;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void CompactionJob::NotifyOnSubcompactionCompleted(
|
||||
SubcompactionState* sub_compact) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
if (db_options_.listeners.empty()) {
|
||||
return;
|
||||
@@ -1061,11 +1029,16 @@ void CompactionJob::NotifyOnSubcompactionCompleted(
|
||||
for (const auto& listener : db_options_.listeners) {
|
||||
listener->OnSubcompactionCompleted(info);
|
||||
}
|
||||
#else
|
||||
(void)sub_compact;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
assert(sub_compact);
|
||||
assert(sub_compact->compaction);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (db_options_.compaction_service) {
|
||||
CompactionServiceJobStatus comp_status =
|
||||
ProcessKeyValueCompactionWithCompactionService(sub_compact);
|
||||
@@ -1076,6 +1049,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// fallback to local compaction
|
||||
assert(comp_status == CompactionServiceJobStatus::kUseLocal);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
uint64_t prev_cpu_micros = db_options_.clock->CPUMicros();
|
||||
|
||||
@@ -1116,7 +1090,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
read_options.verify_checksums = true;
|
||||
read_options.fill_cache = false;
|
||||
read_options.rate_limiter_priority = GetRateLimiterPriority();
|
||||
read_options.io_activity = Env::IOActivity::kCompaction;
|
||||
// Compaction iterators shouldn't be confined to a single prefix.
|
||||
// Compactions use Seek() for
|
||||
// (a) concurrent compactions,
|
||||
@@ -1127,17 +1100,17 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// GenSubcompactionBoundaries doesn't strip away the timestamp.
|
||||
size_t ts_sz = cfd->user_comparator()->timestamp_size();
|
||||
if (start.has_value()) {
|
||||
read_options.iterate_lower_bound = &(*start);
|
||||
read_options.iterate_lower_bound = &start.value();
|
||||
if (ts_sz > 0) {
|
||||
start_without_ts = StripTimestampFromUserKey(*start, ts_sz);
|
||||
read_options.iterate_lower_bound = &(*start_without_ts);
|
||||
start_without_ts = StripTimestampFromUserKey(start.value(), ts_sz);
|
||||
read_options.iterate_lower_bound = &start_without_ts.value();
|
||||
}
|
||||
}
|
||||
if (end.has_value()) {
|
||||
read_options.iterate_upper_bound = &(*end);
|
||||
read_options.iterate_upper_bound = &end.value();
|
||||
if (ts_sz > 0) {
|
||||
end_without_ts = StripTimestampFromUserKey(*end, ts_sz);
|
||||
read_options.iterate_upper_bound = &(*end_without_ts);
|
||||
end_without_ts = StripTimestampFromUserKey(end.value(), ts_sz);
|
||||
read_options.iterate_upper_bound = &end_without_ts.value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1152,8 +1125,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
IterKey end_ikey;
|
||||
Slice start_slice;
|
||||
Slice end_slice;
|
||||
Slice start_user_key{};
|
||||
Slice end_user_key{};
|
||||
|
||||
static constexpr char kMaxTs[] =
|
||||
"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
@@ -1169,22 +1140,21 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
if (start.has_value()) {
|
||||
start_ikey.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
start_ikey.SetInternalKey(start.value(), kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
if (ts_sz > 0) {
|
||||
start_ikey.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeek,
|
||||
&ts_slice);
|
||||
}
|
||||
start_slice = start_ikey.GetInternalKey();
|
||||
start_user_key = start_ikey.GetUserKey();
|
||||
}
|
||||
if (end.has_value()) {
|
||||
end_ikey.SetInternalKey(*end, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
end_ikey.SetInternalKey(end.value(), kMaxSequenceNumber, kValueTypeForSeek);
|
||||
if (ts_sz > 0) {
|
||||
end_ikey.UpdateInternalKey(kMaxSequenceNumber, kValueTypeForSeek,
|
||||
&ts_slice);
|
||||
}
|
||||
end_slice = end_ikey.GetInternalKey();
|
||||
end_user_key = end_ikey.GetUserKey();
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> clip;
|
||||
@@ -1283,8 +1253,6 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
/*expect_valid_internal_key=*/true, range_del_agg.get(),
|
||||
blob_file_builder.get(), db_options_.allow_data_in_errors,
|
||||
db_options_.enforce_single_del_contracts, manual_compaction_canceled_,
|
||||
sub_compact->compaction
|
||||
->DoesInputReferenceBlobFiles() /* must_count_input_entries */,
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
db_options_.info_log, full_history_ts_low, preserve_time_min_seqno_,
|
||||
preclude_last_level_min_seqno_);
|
||||
@@ -1302,15 +1270,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
[this, sub_compact](CompactionOutputs& outputs) {
|
||||
return this->OpenCompactionOutputFile(sub_compact, outputs);
|
||||
};
|
||||
|
||||
const CompactionFileCloseFunc close_file_func =
|
||||
[this, sub_compact, start_user_key, end_user_key](
|
||||
CompactionOutputs& outputs, const Status& status,
|
||||
const Slice& next_table_min_key) {
|
||||
return this->FinishCompactionOutputFile(
|
||||
status, sub_compact, outputs, next_table_min_key,
|
||||
sub_compact->start.has_value() ? &start_user_key : nullptr,
|
||||
sub_compact->end.has_value() ? &end_user_key : nullptr);
|
||||
[this, sub_compact](CompactionOutputs& outputs, const Status& status,
|
||||
const Slice& next_table_min_key) {
|
||||
return this->FinishCompactionOutputFile(status, sub_compact, outputs,
|
||||
next_table_min_key);
|
||||
};
|
||||
|
||||
Status status;
|
||||
@@ -1321,8 +1285,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
|
||||
// Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
|
||||
// returns true.
|
||||
assert(!end.has_value() ||
|
||||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) < 0);
|
||||
|
||||
assert(!end.has_value() || cfd->user_comparator()->Compare(
|
||||
c_iter->user_key(), end.value()) < 0);
|
||||
|
||||
if (c_iter_stats.num_input_records % kRecordStatsEvery ==
|
||||
kRecordStatsEvery - 1) {
|
||||
@@ -1349,25 +1314,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (c_iter->status().IsManualCompactionPaused()) {
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool stop = false;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionJob::ProcessKeyValueCompaction()::stop",
|
||||
static_cast<void*>(&stop));
|
||||
if (stop) {
|
||||
break;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
|
||||
// This number may not be accurate when CompactionIterator was created
|
||||
// with `must_count_input_entries=false`.
|
||||
assert(!sub_compact->compaction->DoesInputReferenceBlobFiles() ||
|
||||
c_iter->HasNumInputEntryScanned());
|
||||
sub_compact->compaction_job_stats.has_num_input_records =
|
||||
c_iter->HasNumInputEntryScanned();
|
||||
sub_compact->compaction_job_stats.num_input_records =
|
||||
c_iter->NumInputEntryScanned();
|
||||
sub_compact->compaction_job_stats.num_blobs_read =
|
||||
c_iter_stats.num_blobs_read;
|
||||
sub_compact->compaction_job_stats.total_blob_bytes_read =
|
||||
@@ -1517,8 +1465,7 @@ void CompactionJob::RecordDroppedKeys(
|
||||
|
||||
Status CompactionJob::FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs, const Slice& next_table_min_key,
|
||||
const Slice* comp_start_user_key, const Slice* comp_end_user_key) {
|
||||
CompactionOutputs& outputs, const Slice& next_table_min_key) {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_SYNC_FILE);
|
||||
assert(sub_compact != nullptr);
|
||||
@@ -1548,10 +1495,12 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// output_to_penultimate_level compaction here, as it's only used to decide
|
||||
// if range dels could be dropped.
|
||||
if (outputs.HasRangeDel()) {
|
||||
s = outputs.AddRangeDels(comp_start_user_key, comp_end_user_key,
|
||||
range_del_out_stats, bottommost_level_,
|
||||
cfd->internal_comparator(), earliest_snapshot,
|
||||
next_table_min_key, full_history_ts_low_);
|
||||
s = outputs.AddRangeDels(
|
||||
sub_compact->start.has_value() ? &(sub_compact->start.value())
|
||||
: nullptr,
|
||||
sub_compact->end.has_value() ? &(sub_compact->end.value()) : nullptr,
|
||||
range_del_out_stats, bottommost_level_, cfd->internal_comparator(),
|
||||
earliest_snapshot, next_table_min_key, full_history_ts_low_);
|
||||
}
|
||||
RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats);
|
||||
TEST_SYNC_POINT("CompactionJob::FinishCompactionOutputFile1");
|
||||
@@ -1663,6 +1612,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
TableFileCreationReason::kCompaction, status_for_listener, file_checksum,
|
||||
file_checksum_func_name);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Report new file to SstFileManagerImpl
|
||||
auto sfm =
|
||||
static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
|
||||
@@ -1681,6 +1631,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
db_error_handler_->SetBGError(s, BackgroundErrorReason::kCompaction);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
outputs.ResetBuilder();
|
||||
return s;
|
||||
@@ -1692,7 +1643,6 @@ Status CompactionJob::InstallCompactionResults(
|
||||
|
||||
db_mutex_->AssertHeld();
|
||||
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
auto* compaction = compact_->compaction;
|
||||
assert(compaction);
|
||||
|
||||
@@ -1770,8 +1720,8 @@ Status CompactionJob::InstallCompactionResults(
|
||||
}
|
||||
|
||||
return versions_->LogAndApply(compaction->column_family_data(),
|
||||
mutable_cf_options, read_options, edit,
|
||||
db_mutex_, db_directory_);
|
||||
mutable_cf_options, edit, db_mutex_,
|
||||
db_directory_);
|
||||
}
|
||||
|
||||
void CompactionJob::RecordCompactionIOStats() {
|
||||
@@ -1806,9 +1756,11 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
std::string fname = GetTableFileName(file_number);
|
||||
// Fire events.
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
#ifndef ROCKSDB_LITE
|
||||
EventHelpers::NotifyTableFileCreationStarted(
|
||||
cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname, job_id_,
|
||||
TableFileCreationReason::kCompaction);
|
||||
#endif // !ROCKSDB_LITE
|
||||
// Make the output file
|
||||
std::unique_ptr<FSWritableFile> writable_file;
|
||||
#ifndef NDEBUG
|
||||
@@ -1867,10 +1819,10 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
InternalKey tmp_start, tmp_end;
|
||||
if (sub_compact->start.has_value()) {
|
||||
tmp_start.SetMinPossibleForUserKey(*(sub_compact->start));
|
||||
tmp_start.SetMinPossibleForUserKey(sub_compact->start.value());
|
||||
}
|
||||
if (sub_compact->end.has_value()) {
|
||||
tmp_end.SetMinPossibleForUserKey(*(sub_compact->end));
|
||||
tmp_end.SetMinPossibleForUserKey(sub_compact->end.value());
|
||||
}
|
||||
uint64_t oldest_ancester_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime(
|
||||
@@ -1881,14 +1833,12 @@ Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
}
|
||||
|
||||
// Initialize a SubcompactionState::Output and add it to sub_compact->outputs
|
||||
uint64_t epoch_number = sub_compact->compaction->MinInputFileEpochNumber();
|
||||
{
|
||||
FileMetaData meta;
|
||||
meta.fd = FileDescriptor(file_number,
|
||||
sub_compact->compaction->output_path_id(), 0);
|
||||
meta.oldest_ancester_time = oldest_ancester_time;
|
||||
meta.file_creation_time = current_time;
|
||||
meta.epoch_number = epoch_number;
|
||||
meta.temperature = temperature;
|
||||
assert(!db_id_.empty());
|
||||
assert(!db_session_id_.empty());
|
||||
@@ -1945,6 +1895,7 @@ void CompactionJob::CleanupCompaction() {
|
||||
compact_ = nullptr;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
assert(prefix_length > 0);
|
||||
@@ -1953,53 +1904,25 @@ void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void CompactionJob::UpdateCompactionStats() {
|
||||
assert(compact_);
|
||||
|
||||
Compaction* compaction = compact_->compaction;
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels = 0;
|
||||
compaction_stats_.stats.num_input_files_in_output_level = 0;
|
||||
|
||||
bool has_error = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
const auto& input_table_properties = compaction->GetTableProperties();
|
||||
for (int input_level = 0;
|
||||
input_level < static_cast<int>(compaction->num_input_levels());
|
||||
++input_level) {
|
||||
size_t num_input_files = compaction->num_input_files(input_level);
|
||||
uint64_t* bytes_read;
|
||||
if (compaction->level(input_level) != compaction->output_level()) {
|
||||
compaction_stats_.stats.num_input_files_in_non_output_levels +=
|
||||
static_cast<int>(num_input_files);
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_non_output_levels;
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.stats.num_input_files_in_non_output_levels,
|
||||
&compaction_stats_.stats.bytes_read_non_output_levels, input_level);
|
||||
} else {
|
||||
compaction_stats_.stats.num_input_files_in_output_level +=
|
||||
static_cast<int>(num_input_files);
|
||||
bytes_read = &compaction_stats_.stats.bytes_read_output_level;
|
||||
}
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const FileMetaData* file_meta = compaction->input(input_level, i);
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
uint64_t file_input_entries = file_meta->num_entries;
|
||||
uint64_t file_num_range_del = file_meta->num_range_deletions;
|
||||
if (file_input_entries == 0) {
|
||||
uint64_t file_number = file_meta->fd.GetNumber();
|
||||
// Try getting info from table property
|
||||
std::string fn =
|
||||
TableFileName(compaction->immutable_options()->cf_paths,
|
||||
file_number, file_meta->fd.GetPathId());
|
||||
const auto& tp = input_table_properties.find(fn);
|
||||
if (tp != input_table_properties.end()) {
|
||||
file_input_entries = tp->second->num_entries;
|
||||
file_num_range_del = tp->second->num_range_deletions;
|
||||
} else {
|
||||
has_error = true;
|
||||
}
|
||||
}
|
||||
compaction_stats_.stats.num_input_records += file_input_entries;
|
||||
if (num_input_range_del) {
|
||||
*num_input_range_del += file_num_range_del;
|
||||
}
|
||||
UpdateCompactionInputStatsHelper(
|
||||
&compaction_stats_.stats.num_input_files_in_output_level,
|
||||
&compaction_stats_.stats.bytes_read_output_level, input_level);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2009,11 +1932,26 @@ bool CompactionJob::UpdateCompactionStats(uint64_t* num_input_range_del) {
|
||||
|
||||
compaction_stats_.stats.num_dropped_records =
|
||||
compaction_stats_.DroppedRecords();
|
||||
return !has_error;
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionInputStatsHelper(int* num_files,
|
||||
uint64_t* bytes_read,
|
||||
int input_level) {
|
||||
const Compaction* compaction = compact_->compaction;
|
||||
auto num_input_files = compaction->num_input_files(input_level);
|
||||
*num_files += static_cast<int>(num_input_files);
|
||||
|
||||
for (size_t i = 0; i < num_input_files; ++i) {
|
||||
const auto* file_meta = compaction->input(input_level, i);
|
||||
*bytes_read += file_meta->fd.GetFileSize();
|
||||
compaction_stats_.stats.num_input_records +=
|
||||
static_cast<uint64_t>(file_meta->num_entries);
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionJob::UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const {
|
||||
#ifndef ROCKSDB_LITE
|
||||
compaction_job_stats_->elapsed_micros = stats.micros;
|
||||
|
||||
// input information
|
||||
@@ -2040,6 +1978,9 @@ void CompactionJob::UpdateCompactionJobStats(
|
||||
CopyPrefix(compact_->LargestUserKey(), CompactionJobStats::kMaxPrefixLength,
|
||||
&compaction_job_stats_->largest_output_key_prefix);
|
||||
}
|
||||
#else
|
||||
(void)stats;
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
void CompactionJob::LogCompaction() {
|
||||
|
||||
@@ -192,21 +192,7 @@ class CompactionJob {
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
protected:
|
||||
// Update the following stats in compaction_stats_.stats
|
||||
// - num_input_files_in_non_output_levels
|
||||
// - num_input_files_in_output_level
|
||||
// - bytes_read_non_output_levels
|
||||
// - bytes_read_output_level
|
||||
// - num_input_records
|
||||
// - bytes_read_blob
|
||||
// - num_dropped_records
|
||||
//
|
||||
// @param num_input_range_del if non-null, will be set to the number of range
|
||||
// deletion entries in this compaction input.
|
||||
//
|
||||
// Returns true iff compaction_stats_.stats.num_input_records and
|
||||
// num_input_range_del are calculated successfully.
|
||||
bool UpdateCompactionStats(uint64_t* num_input_range_del = nullptr);
|
||||
void UpdateCompactionStats();
|
||||
void LogCompaction();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
@@ -270,17 +256,18 @@ class CompactionJob {
|
||||
Status FinishCompactionOutputFile(const Status& input_status,
|
||||
SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs,
|
||||
const Slice& next_table_min_key,
|
||||
const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key);
|
||||
const Slice& next_table_min_key);
|
||||
Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options);
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact,
|
||||
CompactionOutputs& outputs);
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
void UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level);
|
||||
|
||||
void NotifyOnSubcompactionBegin(SubcompactionState* sub_compact);
|
||||
|
||||
void NotifyOnSubcompactionCompleted(SubcompactionState* sub_compact);
|
||||
@@ -415,7 +402,6 @@ struct CompactionServiceOutputFile {
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time;
|
||||
uint64_t file_creation_time;
|
||||
uint64_t epoch_number;
|
||||
uint64_t paranoid_hash;
|
||||
bool marked_for_compaction;
|
||||
UniqueId64x2 unique_id;
|
||||
@@ -425,8 +411,8 @@ struct CompactionServiceOutputFile {
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _epoch_number, uint64_t _paranoid_hash,
|
||||
bool _marked_for_compaction, UniqueId64x2 _unique_id)
|
||||
uint64_t _paranoid_hash, bool _marked_for_compaction,
|
||||
UniqueId64x2 _unique_id)
|
||||
: file_name(name),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
@@ -434,7 +420,6 @@ struct CompactionServiceOutputFile {
|
||||
largest_internal_key(std::move(_largest_internal_key)),
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
epoch_number(_epoch_number),
|
||||
paranoid_hash(_paranoid_hash),
|
||||
marked_for_compaction(_marked_for_compaction),
|
||||
unique_id(std::move(_unique_id)) {}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/cache.h"
|
||||
@@ -54,11 +54,12 @@
|
||||
#include "util/compression.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/rate_limiter_impl.h"
|
||||
#include "util/rate_limiter.h"
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
#if !defined(IOS_CROSS_COMPILE)
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
static std::string RandomString(Random* rnd, int len, double ratio) {
|
||||
@@ -154,8 +155,9 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
ASSERT_OK(TryReopenWithColumnFamilies(cfs, options));
|
||||
}
|
||||
|
||||
Status TryReopenWithColumnFamilies(const std::vector<std::string>& cfs,
|
||||
const std::vector<Options>& options) {
|
||||
Status TryReopenWithColumnFamilies(
|
||||
const std::vector<std::string>& cfs,
|
||||
const std::vector<Options>& options) {
|
||||
Close();
|
||||
EXPECT_EQ(cfs.size(), options.size());
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
@@ -173,7 +175,9 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
return TryReopenWithColumnFamilies(cfs, v_opts);
|
||||
}
|
||||
|
||||
void Reopen(const Options& options) { ASSERT_OK(TryReopen(options)); }
|
||||
void Reopen(const Options& options) {
|
||||
ASSERT_OK(TryReopen(options));
|
||||
}
|
||||
|
||||
void Close() {
|
||||
for (auto h : handles_) {
|
||||
@@ -222,7 +226,9 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
return db_->Put(wo, handles_[cf], k, v);
|
||||
}
|
||||
|
||||
Status Delete(const std::string& k) { return db_->Delete(WriteOptions(), k); }
|
||||
Status Delete(const std::string& k) {
|
||||
return db_->Delete(WriteOptions(), k);
|
||||
}
|
||||
|
||||
Status Delete(int cf, const std::string& k) {
|
||||
return db_->Delete(WriteOptions(), handles_[cf], k);
|
||||
@@ -332,21 +338,21 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
}
|
||||
}
|
||||
|
||||
static void SetDeletionCompactionStats(CompactionJobStats* stats,
|
||||
uint64_t input_deletions,
|
||||
uint64_t expired_deletions,
|
||||
uint64_t records_replaced) {
|
||||
static void SetDeletionCompactionStats(
|
||||
CompactionJobStats *stats, uint64_t input_deletions,
|
||||
uint64_t expired_deletions, uint64_t records_replaced) {
|
||||
stats->num_input_deletion_records = input_deletions;
|
||||
stats->num_expired_deletion_records = expired_deletions;
|
||||
stats->num_records_replaced = records_replaced;
|
||||
}
|
||||
|
||||
void MakeTableWithKeyValues(Random* rnd, uint64_t smallest, uint64_t largest,
|
||||
int key_size, int value_size, uint64_t interval,
|
||||
double ratio, int cf = 0) {
|
||||
void MakeTableWithKeyValues(
|
||||
Random* rnd, uint64_t smallest, uint64_t largest,
|
||||
int key_size, int value_size, uint64_t interval,
|
||||
double ratio, int cf = 0) {
|
||||
for (auto key = smallest; key < largest; key += interval) {
|
||||
ASSERT_OK(Put(cf, Slice(Key(key, key_size)),
|
||||
Slice(RandomString(rnd, value_size, ratio))));
|
||||
Slice(RandomString(rnd, value_size, ratio))));
|
||||
}
|
||||
ASSERT_OK(Flush(cf));
|
||||
}
|
||||
@@ -355,9 +361,9 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
// rounds of keys are inserted into the database, as per the behavior
|
||||
// of the DeletionStatsTest.
|
||||
void SelectivelyDeleteKeys(uint64_t smallest, uint64_t largest,
|
||||
uint64_t interval, int deletion_interval,
|
||||
int key_size, uint64_t cutoff_key_num,
|
||||
CompactionJobStats* stats, int cf = 0) {
|
||||
uint64_t interval, int deletion_interval, int key_size,
|
||||
uint64_t cutoff_key_num, CompactionJobStats* stats, int cf = 0) {
|
||||
|
||||
// interval needs to be >= 2 so that deletion entries can be inserted
|
||||
// that are intended to not result in an actual key deletion by using
|
||||
// an offset of 1 from another existing key
|
||||
@@ -381,19 +387,20 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
|
||||
// Insert some deletions for keys that don't exist that
|
||||
// are both in and out of the key range
|
||||
ASSERT_OK(Delete(cf, Key(smallest + 1, key_size)));
|
||||
ASSERT_OK(Delete(cf, Key(smallest+1, key_size)));
|
||||
deletions_made++;
|
||||
|
||||
ASSERT_OK(Delete(cf, Key(smallest - 1, key_size)));
|
||||
ASSERT_OK(Delete(cf, Key(smallest-1, key_size)));
|
||||
deletions_made++;
|
||||
num_expired++;
|
||||
|
||||
ASSERT_OK(Delete(cf, Key(smallest - 9, key_size)));
|
||||
ASSERT_OK(Delete(cf, Key(smallest-9, key_size)));
|
||||
deletions_made++;
|
||||
num_expired++;
|
||||
|
||||
ASSERT_OK(Flush(cf));
|
||||
SetDeletionCompactionStats(stats, deletions_made, num_expired, num_deleted);
|
||||
SetDeletionCompactionStats(stats, deletions_made, num_expired,
|
||||
num_deleted);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -433,20 +440,25 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
// use ASSERT_GE and ASSERT_LE with a reasonable bias ---
|
||||
// 10% in uncompressed case and 20% when compression is used.
|
||||
virtual void Verify(const CompactionJobStats& current_stats,
|
||||
const CompactionJobStats& stats) {
|
||||
const CompactionJobStats& stats) {
|
||||
// time
|
||||
ASSERT_GT(current_stats.elapsed_micros, 0U);
|
||||
|
||||
ASSERT_EQ(current_stats.num_input_records, stats.num_input_records);
|
||||
ASSERT_EQ(current_stats.num_input_files, stats.num_input_files);
|
||||
ASSERT_EQ(current_stats.num_input_records,
|
||||
stats.num_input_records);
|
||||
ASSERT_EQ(current_stats.num_input_files,
|
||||
stats.num_input_files);
|
||||
ASSERT_EQ(current_stats.num_input_files_at_output_level,
|
||||
stats.num_input_files_at_output_level);
|
||||
stats.num_input_files_at_output_level);
|
||||
|
||||
ASSERT_EQ(current_stats.num_output_records, stats.num_output_records);
|
||||
ASSERT_EQ(current_stats.num_output_files, stats.num_output_files);
|
||||
ASSERT_EQ(current_stats.num_output_records,
|
||||
stats.num_output_records);
|
||||
ASSERT_EQ(current_stats.num_output_files,
|
||||
stats.num_output_files);
|
||||
|
||||
ASSERT_EQ(current_stats.is_full_compaction, stats.is_full_compaction);
|
||||
ASSERT_EQ(current_stats.is_manual_compaction, stats.is_manual_compaction);
|
||||
ASSERT_EQ(current_stats.is_manual_compaction,
|
||||
stats.is_manual_compaction);
|
||||
|
||||
// file size
|
||||
double kFileSizeBias = compression_enabled_ ? 0.20 : 0.10;
|
||||
@@ -463,14 +475,18 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
ASSERT_EQ(current_stats.total_input_raw_value_bytes,
|
||||
stats.total_input_raw_value_bytes);
|
||||
|
||||
ASSERT_EQ(current_stats.num_records_replaced, stats.num_records_replaced);
|
||||
ASSERT_EQ(current_stats.num_records_replaced,
|
||||
stats.num_records_replaced);
|
||||
|
||||
ASSERT_EQ(current_stats.num_corrupt_keys, stats.num_corrupt_keys);
|
||||
ASSERT_EQ(current_stats.num_corrupt_keys,
|
||||
stats.num_corrupt_keys);
|
||||
|
||||
ASSERT_EQ(std::string(current_stats.smallest_output_key_prefix),
|
||||
std::string(stats.smallest_output_key_prefix));
|
||||
ASSERT_EQ(std::string(current_stats.largest_output_key_prefix),
|
||||
std::string(stats.largest_output_key_prefix));
|
||||
ASSERT_EQ(
|
||||
std::string(current_stats.smallest_output_key_prefix),
|
||||
std::string(stats.smallest_output_key_prefix));
|
||||
ASSERT_EQ(
|
||||
std::string(current_stats.largest_output_key_prefix),
|
||||
std::string(stats.largest_output_key_prefix));
|
||||
}
|
||||
|
||||
// Add an expected compaction stats, which will be used to
|
||||
@@ -481,7 +497,9 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
expected_stats_.push(stats);
|
||||
}
|
||||
|
||||
void EnableCompression(bool flag) { compression_enabled_ = flag; }
|
||||
void EnableCompression(bool flag) {
|
||||
compression_enabled_ = flag;
|
||||
}
|
||||
|
||||
bool verify_next_comp_io_stats() const { return verify_next_comp_io_stats_; }
|
||||
|
||||
@@ -499,37 +517,45 @@ class CompactionJobDeletionStatsChecker : public CompactionJobStatsChecker {
|
||||
// Verifies whether two CompactionJobStats match.
|
||||
void Verify(const CompactionJobStats& current_stats,
|
||||
const CompactionJobStats& stats) override {
|
||||
ASSERT_EQ(current_stats.num_input_deletion_records,
|
||||
stats.num_input_deletion_records);
|
||||
ASSERT_EQ(current_stats.num_expired_deletion_records,
|
||||
stats.num_expired_deletion_records);
|
||||
ASSERT_EQ(current_stats.num_records_replaced, stats.num_records_replaced);
|
||||
ASSERT_EQ(
|
||||
current_stats.num_input_deletion_records,
|
||||
stats.num_input_deletion_records);
|
||||
ASSERT_EQ(
|
||||
current_stats.num_expired_deletion_records,
|
||||
stats.num_expired_deletion_records);
|
||||
ASSERT_EQ(
|
||||
current_stats.num_records_replaced,
|
||||
stats.num_records_replaced);
|
||||
|
||||
ASSERT_EQ(current_stats.num_corrupt_keys, stats.num_corrupt_keys);
|
||||
ASSERT_EQ(current_stats.num_corrupt_keys,
|
||||
stats.num_corrupt_keys);
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
uint64_t EstimatedFileSize(uint64_t num_records, size_t key_size,
|
||||
size_t value_size, double compression_ratio = 1.0,
|
||||
size_t block_size = 4096,
|
||||
int bloom_bits_per_key = 10) {
|
||||
uint64_t EstimatedFileSize(
|
||||
uint64_t num_records, size_t key_size, size_t value_size,
|
||||
double compression_ratio = 1.0,
|
||||
size_t block_size = 4096,
|
||||
int bloom_bits_per_key = 10) {
|
||||
const size_t kPerKeyOverhead = 8;
|
||||
const size_t kFooterSize = 512;
|
||||
|
||||
uint64_t data_size = static_cast<uint64_t>(
|
||||
num_records *
|
||||
(key_size + value_size * compression_ratio + kPerKeyOverhead));
|
||||
uint64_t data_size =
|
||||
static_cast<uint64_t>(
|
||||
num_records * (key_size + value_size * compression_ratio +
|
||||
kPerKeyOverhead));
|
||||
|
||||
return data_size + kFooterSize +
|
||||
num_records * bloom_bits_per_key / 8 // filter block
|
||||
return data_size + kFooterSize
|
||||
+ num_records * bloom_bits_per_key / 8 // filter block
|
||||
+ data_size * (key_size + 8) / block_size; // index block
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
void CopyPrefix(
|
||||
const Slice& src, size_t prefix_length, std::string* dst) {
|
||||
assert(prefix_length > 0);
|
||||
size_t length = src.size() > prefix_length ? prefix_length : src.size();
|
||||
dst->assign(src.data(), length);
|
||||
@@ -555,24 +581,28 @@ CompactionJobStats NewManualCompactionJobStats(
|
||||
stats.num_output_files = num_output_files;
|
||||
|
||||
stats.total_input_bytes =
|
||||
EstimatedFileSize(num_input_records / num_input_files, key_size,
|
||||
value_size, compression_ratio) *
|
||||
num_input_files;
|
||||
EstimatedFileSize(
|
||||
num_input_records / num_input_files,
|
||||
key_size, value_size, compression_ratio) * num_input_files;
|
||||
stats.total_output_bytes =
|
||||
EstimatedFileSize(num_output_records / num_output_files, key_size,
|
||||
value_size, compression_ratio) *
|
||||
num_output_files;
|
||||
stats.total_input_raw_key_bytes = num_input_records * (key_size + 8);
|
||||
stats.total_input_raw_value_bytes = num_input_records * value_size;
|
||||
EstimatedFileSize(
|
||||
num_output_records / num_output_files,
|
||||
key_size, value_size, compression_ratio) * num_output_files;
|
||||
stats.total_input_raw_key_bytes =
|
||||
num_input_records * (key_size + 8);
|
||||
stats.total_input_raw_value_bytes =
|
||||
num_input_records * value_size;
|
||||
|
||||
stats.is_full_compaction = is_full;
|
||||
stats.is_manual_compaction = is_manual;
|
||||
|
||||
stats.num_records_replaced = num_records_replaced;
|
||||
|
||||
CopyPrefix(smallest_key, CompactionJobStats::kMaxPrefixLength,
|
||||
CopyPrefix(smallest_key,
|
||||
CompactionJobStats::kMaxPrefixLength,
|
||||
&stats.smallest_output_key_prefix);
|
||||
CopyPrefix(largest_key, CompactionJobStats::kMaxPrefixLength,
|
||||
CopyPrefix(largest_key,
|
||||
CompactionJobStats::kMaxPrefixLength,
|
||||
&stats.largest_output_key_prefix);
|
||||
|
||||
return stats;
|
||||
@@ -616,7 +646,6 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
// via AddExpectedStats().
|
||||
auto* stats_checker = new CompactionJobStatsChecker();
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.listeners.emplace_back(stats_checker);
|
||||
options.create_if_missing = true;
|
||||
// just enough setting to hold off auto-compaction.
|
||||
@@ -633,11 +662,13 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
|
||||
// 1st Phase: generate "num_L0_files" L0 files.
|
||||
int num_L0_files = 0;
|
||||
for (uint64_t start_key = key_base; start_key <= key_base * kTestScale;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(&rnd, start_key, start_key + key_base - 1,
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base - 1,
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
snprintf(buf, kBufSize, "%d", ++num_L0_files);
|
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1));
|
||||
}
|
||||
@@ -653,9 +684,13 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
start_key += key_base, count++) {
|
||||
smallest_key = Key(start_key, 10);
|
||||
largest_key = Key(start_key + key_base - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(NewManualCompactionJobStats(
|
||||
smallest_key, largest_key, 1, 0, num_keys_per_L0_file, kKeySize,
|
||||
kValueSize, 1, num_keys_per_L0_file, compression_ratio, 0));
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
smallest_key, largest_key,
|
||||
1, 0, num_keys_per_L0_file,
|
||||
kKeySize, kValueSize,
|
||||
1, num_keys_per_L0_file,
|
||||
compression_ratio, 0));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
TEST_Compact(0, 1, smallest_key, largest_key);
|
||||
snprintf(buf, kBufSize, "%d,%d", num_L0_files - count, count);
|
||||
@@ -666,10 +701,14 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
int num_remaining_L0 = num_L0_files - L0_compaction_count;
|
||||
smallest_key = Key(key_base * (L0_compaction_count + 1), 10);
|
||||
largest_key = Key(key_base * (kTestScale + 1) - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(NewManualCompactionJobStats(
|
||||
smallest_key, largest_key, num_remaining_L0, 0,
|
||||
num_keys_per_L0_file * num_remaining_L0, kKeySize, kValueSize, 1,
|
||||
num_keys_per_L0_file * num_remaining_L0, compression_ratio, 0));
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
smallest_key, largest_key,
|
||||
num_remaining_L0,
|
||||
0, num_keys_per_L0_file * num_remaining_L0,
|
||||
kKeySize, kValueSize,
|
||||
1, num_keys_per_L0_file * num_remaining_L0,
|
||||
compression_ratio, 0));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
TEST_Compact(0, 1, smallest_key, largest_key);
|
||||
|
||||
@@ -680,11 +719,13 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
|
||||
// 3rd Phase: generate sparse L0 files (wider key-range, same num of keys)
|
||||
int sparseness = 2;
|
||||
for (uint64_t start_key = key_base; start_key <= key_base * kTestScale;
|
||||
start_key += key_base * sparseness) {
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale;
|
||||
start_key += key_base * sparseness) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base * sparseness - 1, kKeySize,
|
||||
kValueSize, key_base * sparseness / num_keys_per_L0_file,
|
||||
&rnd, start_key, start_key + key_base * sparseness - 1,
|
||||
kKeySize, kValueSize,
|
||||
key_base * sparseness / num_keys_per_L0_file,
|
||||
compression_ratio, 1);
|
||||
snprintf(buf, kBufSize, "%d,%d", ++num_L0_files, num_L1_files);
|
||||
ASSERT_EQ(std::string(buf), FilesPerLevel(1));
|
||||
@@ -696,15 +737,21 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
// output files without coordinating to see if the output could fit into
|
||||
// a smaller number of files like it does when it runs sequentially
|
||||
int num_output_files = options.max_subcompactions > 1 ? 2 : 1;
|
||||
for (uint64_t start_key = key_base; num_L0_files > 1;
|
||||
for (uint64_t start_key = key_base;
|
||||
num_L0_files > 1;
|
||||
start_key += key_base * sparseness) {
|
||||
smallest_key = Key(start_key, 10);
|
||||
largest_key = Key(start_key + key_base * sparseness - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(NewManualCompactionJobStats(
|
||||
smallest_key, largest_key, 3, 2, num_keys_per_L0_file * 3, kKeySize,
|
||||
kValueSize, num_output_files,
|
||||
num_keys_per_L0_file * 2, // 1/3 of the data will be updated.
|
||||
compression_ratio, num_keys_per_L0_file));
|
||||
largest_key =
|
||||
Key(start_key + key_base * sparseness - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
smallest_key, largest_key,
|
||||
3, 2, num_keys_per_L0_file * 3,
|
||||
kKeySize, kValueSize,
|
||||
num_output_files,
|
||||
num_keys_per_L0_file * 2, // 1/3 of the data will be updated.
|
||||
compression_ratio,
|
||||
num_keys_per_L0_file));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
Compact(1, smallest_key, largest_key);
|
||||
if (options.max_subcompactions == 1) {
|
||||
@@ -719,10 +766,14 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
// In the first sub-compaction, we expect L0 compaction.
|
||||
smallest_key = Key(key_base, 10);
|
||||
largest_key = Key(key_base * (kTestScale + 1) - key_interval, 10);
|
||||
stats_checker->AddExpectedStats(NewManualCompactionJobStats(
|
||||
Key(key_base * (kTestScale + 1 - sparseness), 10), largest_key, 2, 1,
|
||||
num_keys_per_L0_file * 3, kKeySize, kValueSize, 1,
|
||||
num_keys_per_L0_file * 2, compression_ratio, num_keys_per_L0_file));
|
||||
stats_checker->AddExpectedStats(
|
||||
NewManualCompactionJobStats(
|
||||
Key(key_base * (kTestScale + 1 - sparseness), 10), largest_key,
|
||||
2, 1, num_keys_per_L0_file * 3,
|
||||
kKeySize, kValueSize,
|
||||
1, num_keys_per_L0_file * 2,
|
||||
compression_ratio,
|
||||
num_keys_per_L0_file));
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 1U);
|
||||
Compact(1, smallest_key, largest_key);
|
||||
|
||||
@@ -816,10 +867,9 @@ TEST_P(CompactionJobStatsTest, DeletionStatsTest) {
|
||||
// what we expect.
|
||||
auto* stats_checker = new CompactionJobDeletionStatsChecker();
|
||||
Options options;
|
||||
options.level_compaction_dynamic_level_bytes = false;
|
||||
options.listeners.emplace_back(stats_checker);
|
||||
options.create_if_missing = true;
|
||||
options.level0_file_num_compaction_trigger = kTestScale + 1;
|
||||
options.level0_file_num_compaction_trigger = kTestScale+1;
|
||||
options.num_levels = 3;
|
||||
options.compression = kNoCompression;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
@@ -831,10 +881,13 @@ TEST_P(CompactionJobStatsTest, DeletionStatsTest) {
|
||||
// Stage 1: Generate several L0 files and then send them to L2 by
|
||||
// using CompactRangeOptions and CompactRange(). These files will
|
||||
// have a strict subset of the keys from the full key-range
|
||||
for (uint64_t start_key = key_base; start_key <= key_base * kTestScale / 2;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(&rnd, start_key, start_key + key_base - 1, kKeySize,
|
||||
kValueSize, key_interval, compression_ratio, 1);
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale / 2;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base - 1,
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
}
|
||||
|
||||
CompactRangeOptions cr_options;
|
||||
@@ -844,10 +897,13 @@ TEST_P(CompactionJobStatsTest, DeletionStatsTest) {
|
||||
ASSERT_GT(NumTableFilesAtLevel(2, 1), 0);
|
||||
|
||||
// Stage 2: Generate files including keys from the entire key range
|
||||
for (uint64_t start_key = key_base; start_key <= key_base * kTestScale;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(&rnd, start_key, start_key + key_base - 1, kKeySize,
|
||||
kValueSize, key_interval, compression_ratio, 1);
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base - 1,
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
}
|
||||
|
||||
// Send these L0 files to L1
|
||||
@@ -856,16 +912,16 @@ TEST_P(CompactionJobStatsTest, DeletionStatsTest) {
|
||||
|
||||
// Add a new record and flush so now there is a L0 file
|
||||
// with a value too (not just deletions from the next step)
|
||||
ASSERT_OK(Put(1, Key(key_base - 6, kKeySize), "test"));
|
||||
ASSERT_OK(Put(1, Key(key_base-6, kKeySize), "test"));
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// Stage 3: Generate L0 files with some deletions so now
|
||||
// there are files with the same key range in L0, L1, and L2
|
||||
int deletion_interval = 3;
|
||||
CompactionJobStats first_compaction_stats;
|
||||
SelectivelyDeleteKeys(key_base, largest_key_num, key_interval,
|
||||
deletion_interval, kKeySize, cutoff_key_num,
|
||||
&first_compaction_stats, 1);
|
||||
SelectivelyDeleteKeys(key_base, largest_key_num,
|
||||
key_interval, deletion_interval, kKeySize, cutoff_key_num,
|
||||
&first_compaction_stats, 1);
|
||||
|
||||
stats_checker->AddExpectedStats(first_compaction_stats);
|
||||
|
||||
@@ -876,7 +932,8 @@ TEST_P(CompactionJobStatsTest, DeletionStatsTest) {
|
||||
namespace {
|
||||
int GetUniversalCompactionInputUnits(uint32_t num_flushes) {
|
||||
uint32_t compaction_input_units;
|
||||
for (compaction_input_units = 1; num_flushes >= compaction_input_units;
|
||||
for (compaction_input_units = 1;
|
||||
num_flushes >= compaction_input_units;
|
||||
compaction_input_units *= 2) {
|
||||
if ((num_flushes & compaction_input_units) != 0) {
|
||||
return compaction_input_units > 1 ? compaction_input_units : 0;
|
||||
@@ -941,10 +998,13 @@ TEST_P(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
}
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 3U);
|
||||
|
||||
for (uint64_t start_key = key_base; start_key <= key_base * kTestScale;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(&rnd, start_key, start_key + key_base - 1, kKeySize,
|
||||
kValueSize, key_interval, compression_ratio, 1);
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale;
|
||||
start_key += key_base) {
|
||||
MakeTableWithKeyValues(
|
||||
&rnd, start_key, start_key + key_base - 1,
|
||||
kKeySize, kValueSize, key_interval,
|
||||
compression_ratio, 1);
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db_)->TEST_WaitForCompact());
|
||||
}
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 0U);
|
||||
@@ -960,6 +1020,15 @@ int main(int argc, char** argv) {
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED, not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
#else
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/compaction/compaction_job.h"
|
||||
|
||||
@@ -39,7 +40,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
|
||||
void VerifyInitializationOfCompactionJobStats(
|
||||
const CompactionJobStats& compaction_job_stats) {
|
||||
const CompactionJobStats& compaction_job_stats) {
|
||||
#if !defined(IOS_CROSS_COMPILE)
|
||||
ASSERT_EQ(compaction_job_stats.elapsed_micros, 0U);
|
||||
|
||||
@@ -373,26 +374,22 @@ class CompactionJobTestBase : public testing::Test {
|
||||
} else if (table_type_ == TableTypeForTest::kMockTable) {
|
||||
file_size = 10;
|
||||
EXPECT_OK(mock_table_factory_->CreateMockTable(
|
||||
env_, GenerateFileName(file_number), contents));
|
||||
env_, GenerateFileName(file_number), std::move(contents)));
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
VersionEdit edit;
|
||||
edit.AddFile(
|
||||
level, file_number, 0, file_size, smallest_key, largest_key,
|
||||
smallest_seqno, largest_seqno, false, Temperature::kUnknown,
|
||||
oldest_blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime,
|
||||
versions_->GetColumnFamilySet()->GetDefault()->NewEpochNumber(),
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName, kNullUniqueId64x2,
|
||||
/*compensated_range_deletion_size=*/0, /*tail_size=*/0,
|
||||
/*user_defined_timestamps_persisted=*/true);
|
||||
edit.AddFile(level, file_number, 0, file_size, smallest_key, largest_key,
|
||||
smallest_seqno, largest_seqno, false, Temperature::kUnknown,
|
||||
oldest_blob_file_number, kUnknownOldestAncesterTime,
|
||||
kUnknownFileCreationTime, kUnknownFileChecksum,
|
||||
kUnknownFileChecksumFuncName, kNullUniqueId64x2);
|
||||
|
||||
mutex_.Lock();
|
||||
EXPECT_OK(versions_->LogAndApply(
|
||||
versions_->GetColumnFamilySet()->GetDefault(), mutable_cf_options_,
|
||||
read_options_, &edit, &mutex_, nullptr));
|
||||
EXPECT_OK(
|
||||
versions_->LogAndApply(versions_->GetColumnFamilySet()->GetDefault(),
|
||||
mutable_cf_options_, &edit, &mutex_, nullptr));
|
||||
mutex_.Unlock();
|
||||
}
|
||||
|
||||
@@ -455,8 +452,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
Status s = cf_options_.table_factory->NewTableReader(
|
||||
read_opts,
|
||||
TableReaderOptions(*cfd->ioptions(), nullptr, FileOptions(),
|
||||
cfd_->internal_comparator(),
|
||||
0 /* block_protection_bytes_per_key */),
|
||||
cfd_->internal_comparator()),
|
||||
std::move(freader), file_size, &table_reader, false);
|
||||
ASSERT_OK(s);
|
||||
assert(table_reader);
|
||||
@@ -503,7 +499,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
|
||||
// This is how the key will look like once it's written in bottommost
|
||||
// file
|
||||
InternalKey bottommost_internal_key(key, 0, kTypeValue);
|
||||
InternalKey bottommost_internal_key(
|
||||
key, 0, kTypeValue);
|
||||
|
||||
if (corrupt_id(k)) {
|
||||
test::CorruptKeyType(&internal_key);
|
||||
@@ -623,7 +620,7 @@ class CompactionJobTestBase : public testing::Test {
|
||||
CompactionInputFiles compaction_level;
|
||||
compaction_level.level = input_levels[i];
|
||||
compaction_level.files.insert(compaction_level.files.end(),
|
||||
level_files.begin(), level_files.end());
|
||||
level_files.begin(), level_files.end());
|
||||
compaction_input_files.push_back(compaction_level);
|
||||
num_input_files += level_files.size();
|
||||
}
|
||||
@@ -655,12 +652,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ASSERT_TRUE(full_history_ts_low_.empty() ||
|
||||
ucmp_->timestamp_size() == full_history_ts_low_.size());
|
||||
const std::atomic<bool> kManualCompactionCanceledFalse{false};
|
||||
JobContext job_context(1, false /* create_superversion */);
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, mutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, &log_buffer, nullptr, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, &job_context,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, nullptr,
|
||||
table_cache_, &event_logger, false, false, dbname_,
|
||||
&compaction_job_stats_, Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_canceled=*/kManualCompactionCanceledFalse,
|
||||
@@ -730,7 +726,6 @@ class CompactionJobTestBase : public testing::Test {
|
||||
ColumnFamilyOptions cf_options_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
MutableDBOptions mutable_db_options_;
|
||||
const ReadOptions read_options_;
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
WriteController write_controller_;
|
||||
WriteBufferManager write_buffer_manager_;
|
||||
@@ -1661,7 +1656,7 @@ TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX), rnd.OneIn(2), id);
|
||||
rnd64.Uniform(UINT64_MAX), rnd.OneIn(2), id);
|
||||
}
|
||||
result.output_level = rnd.Uniform(10);
|
||||
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
@@ -2444,3 +2439,14 @@ int main(int argc, char** argv) {
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as CompactionJobStats is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
+203
-349
@@ -43,10 +43,7 @@ Status CompactionOutputs::Finish(const Status& intput_status,
|
||||
const uint64_t current_bytes = builder_->FileSize();
|
||||
if (s.ok()) {
|
||||
meta->fd.file_size = current_bytes;
|
||||
meta->tail_size = builder_->GetTailSize();
|
||||
meta->marked_for_compaction = builder_->NeedCompact();
|
||||
meta->user_defined_timestamps_persisted = static_cast<bool>(
|
||||
builder_->GetTableProperties().user_defined_timestamps_persisted);
|
||||
}
|
||||
current_output().finished = true;
|
||||
stats_.bytes_written += current_bytes;
|
||||
@@ -79,46 +76,6 @@ IOStatus CompactionOutputs::WriterSyncClose(const Status& input_status,
|
||||
return io_s;
|
||||
}
|
||||
|
||||
bool CompactionOutputs::UpdateFilesToCutForTTLStates(
|
||||
const Slice& internal_key) {
|
||||
if (!files_to_cut_for_ttl_.empty()) {
|
||||
const InternalKeyComparator* icmp =
|
||||
&compaction_->column_family_data()->internal_comparator();
|
||||
if (cur_files_to_cut_for_ttl_ != -1) {
|
||||
// Previous key is inside the range of a file
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl_[cur_files_to_cut_for_ttl_]
|
||||
->largest.Encode()) > 0) {
|
||||
next_files_to_cut_for_ttl_ = cur_files_to_cut_for_ttl_ + 1;
|
||||
cur_files_to_cut_for_ttl_ = -1;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Look for the key position
|
||||
while (next_files_to_cut_for_ttl_ <
|
||||
static_cast<int>(files_to_cut_for_ttl_.size())) {
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl_[next_files_to_cut_for_ttl_]
|
||||
->smallest.Encode()) >= 0) {
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl_[next_files_to_cut_for_ttl_]
|
||||
->largest.Encode()) <= 0) {
|
||||
// With in the current file
|
||||
cur_files_to_cut_for_ttl_ = next_files_to_cut_for_ttl_;
|
||||
return true;
|
||||
}
|
||||
// Beyond the current file
|
||||
next_files_to_cut_for_ttl_++;
|
||||
} else {
|
||||
// Still fall into the gap
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
const Slice& internal_key) {
|
||||
size_t curr_key_boundary_switched_num = 0;
|
||||
@@ -127,6 +84,11 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
if (grandparents.empty()) {
|
||||
return curr_key_boundary_switched_num;
|
||||
}
|
||||
assert(!internal_key.empty());
|
||||
InternalKey ikey;
|
||||
ikey.DecodeFrom(internal_key);
|
||||
assert(ikey.Valid());
|
||||
|
||||
const Comparator* ucmp = compaction_->column_family_data()->user_comparator();
|
||||
|
||||
// Move the grandparent_index_ to the file containing the current user_key.
|
||||
@@ -134,7 +96,7 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
// index points to the last file containing the key.
|
||||
while (grandparent_index_ < grandparents.size()) {
|
||||
if (being_grandparent_gap_) {
|
||||
if (sstableKeyCompare(ucmp, internal_key,
|
||||
if (sstableKeyCompare(ucmp, ikey,
|
||||
grandparents[grandparent_index_]->smallest) < 0) {
|
||||
break;
|
||||
}
|
||||
@@ -147,13 +109,13 @@ size_t CompactionOutputs::UpdateGrandparentBoundaryInfo(
|
||||
being_grandparent_gap_ = false;
|
||||
} else {
|
||||
int cmp_result = sstableKeyCompare(
|
||||
ucmp, internal_key, grandparents[grandparent_index_]->largest);
|
||||
ucmp, ikey, grandparents[grandparent_index_]->largest);
|
||||
// If it's same key, make sure grandparent_index_ is pointing to the last
|
||||
// one.
|
||||
if (cmp_result < 0 ||
|
||||
(cmp_result == 0 &&
|
||||
(grandparent_index_ == grandparents.size() - 1 ||
|
||||
sstableKeyCompare(ucmp, internal_key,
|
||||
sstableKeyCompare(ucmp, ikey,
|
||||
grandparents[grandparent_index_ + 1]->smallest) <
|
||||
0))) {
|
||||
break;
|
||||
@@ -223,39 +185,18 @@ uint64_t CompactionOutputs::GetCurrentKeyGrandparentOverlappedBytes(
|
||||
|
||||
bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
assert(c_iter.Valid());
|
||||
|
||||
// always update grandparent information like overlapped file number, size
|
||||
// etc.
|
||||
const Slice& internal_key = c_iter.key();
|
||||
#ifndef NDEBUG
|
||||
bool should_stop = false;
|
||||
std::pair<bool*, const Slice> p{&should_stop, internal_key};
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionOutputs::ShouldStopBefore::manual_decision", (void*)&p);
|
||||
if (should_stop) {
|
||||
return true;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
const uint64_t previous_overlapped_bytes = grandparent_overlapped_bytes_;
|
||||
const InternalKeyComparator* icmp =
|
||||
&compaction_->column_family_data()->internal_comparator();
|
||||
size_t num_grandparent_boundaries_crossed = 0;
|
||||
bool should_stop_for_ttl = false;
|
||||
// Always update grandparent information like overlapped file number, size
|
||||
// etc., and TTL states.
|
||||
// If compaction_->output_level() == 0, there is no need to update grandparent
|
||||
// info, and that `grandparent` should be empty.
|
||||
if (compaction_->output_level() > 0) {
|
||||
num_grandparent_boundaries_crossed =
|
||||
UpdateGrandparentBoundaryInfo(internal_key);
|
||||
should_stop_for_ttl = UpdateFilesToCutForTTLStates(internal_key);
|
||||
}
|
||||
size_t num_grandparent_boundaries_crossed =
|
||||
UpdateGrandparentBoundaryInfo(internal_key);
|
||||
|
||||
if (!HasBuilder()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (should_stop_for_ttl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If there's user defined partitioner, check that first
|
||||
if (partitioner_ && partitioner_->ShouldPartition(PartitionerRequest(
|
||||
last_key_for_partitioner_, c_iter.user_key(),
|
||||
@@ -273,6 +214,9 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const InternalKeyComparator* icmp =
|
||||
&compaction_->column_family_data()->internal_comparator();
|
||||
|
||||
// Check if it needs to split for RoundRobin
|
||||
// Invalid local_output_split_key indicates that we do not need to split
|
||||
if (local_output_split_key_ != nullptr && !is_split_) {
|
||||
@@ -346,6 +290,41 @@ bool CompactionOutputs::ShouldStopBefore(const CompactionIterator& c_iter) {
|
||||
}
|
||||
}
|
||||
|
||||
// check ttl file boundaries if there's any
|
||||
if (!files_to_cut_for_ttl_.empty()) {
|
||||
if (cur_files_to_cut_for_ttl_ != -1) {
|
||||
// Previous key is inside the range of a file
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl_[cur_files_to_cut_for_ttl_]
|
||||
->largest.Encode()) > 0) {
|
||||
next_files_to_cut_for_ttl_ = cur_files_to_cut_for_ttl_ + 1;
|
||||
cur_files_to_cut_for_ttl_ = -1;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Look for the key position
|
||||
while (next_files_to_cut_for_ttl_ <
|
||||
static_cast<int>(files_to_cut_for_ttl_.size())) {
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl_[next_files_to_cut_for_ttl_]
|
||||
->smallest.Encode()) >= 0) {
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl_[next_files_to_cut_for_ttl_]
|
||||
->largest.Encode()) <= 0) {
|
||||
// With in the current file
|
||||
cur_files_to_cut_for_ttl_ = next_files_to_cut_for_ttl_;
|
||||
return true;
|
||||
}
|
||||
// Beyond the current file
|
||||
next_files_to_cut_for_ttl_++;
|
||||
} else {
|
||||
// Still fall into the gap
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -354,14 +333,8 @@ Status CompactionOutputs::AddToOutput(
|
||||
const CompactionFileOpenFunc& open_file_func,
|
||||
const CompactionFileCloseFunc& close_file_func) {
|
||||
Status s;
|
||||
bool is_range_del = c_iter.IsDeleteRangeSentinelKey();
|
||||
if (is_range_del && compaction_->bottommost_level()) {
|
||||
// We don't consider range tombstone for bottommost level since:
|
||||
// 1. there is no grandparent and hence no overlap to consider
|
||||
// 2. range tombstone may be dropped at bottommost level.
|
||||
return s;
|
||||
}
|
||||
const Slice& key = c_iter.key();
|
||||
|
||||
if (ShouldStopBefore(c_iter) && HasBuilder()) {
|
||||
s = close_file_func(*this, c_iter.InputStatus(), key);
|
||||
if (!s.ok()) {
|
||||
@@ -371,13 +344,6 @@ Status CompactionOutputs::AddToOutput(
|
||||
grandparent_boundary_switched_num_ = 0;
|
||||
grandparent_overlapped_bytes_ =
|
||||
GetCurrentKeyGrandparentOverlappedBytes(key);
|
||||
if (UNLIKELY(is_range_del)) {
|
||||
// lower bound for this new output file, this is needed as the lower bound
|
||||
// does not come from the smallest point key in this case.
|
||||
range_tombstone_lower_bound_.DecodeFrom(key);
|
||||
} else {
|
||||
range_tombstone_lower_bound_.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Open output file if necessary
|
||||
@@ -388,17 +354,6 @@ Status CompactionOutputs::AddToOutput(
|
||||
}
|
||||
}
|
||||
|
||||
// c_iter may emit range deletion keys, so update `last_key_for_partitioner_`
|
||||
// here before returning below when `is_range_del` is true
|
||||
if (partitioner_) {
|
||||
last_key_for_partitioner_.assign(c_iter.user_key().data_,
|
||||
c_iter.user_key().size_);
|
||||
}
|
||||
|
||||
if (UNLIKELY(is_range_del)) {
|
||||
return s;
|
||||
}
|
||||
|
||||
assert(builder_ != nullptr);
|
||||
const Slice& value = c_iter.value();
|
||||
s = current_output().validator.Add(key, value);
|
||||
@@ -422,185 +377,116 @@ Status CompactionOutputs::AddToOutput(
|
||||
s = current_output().meta.UpdateBoundaries(key, value, ikey.sequence,
|
||||
ikey.type);
|
||||
|
||||
if (partitioner_) {
|
||||
last_key_for_partitioner_.assign(c_iter.user_key().data_,
|
||||
c_iter.user_key().size_);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace {
|
||||
void SetMaxSeqAndTs(InternalKey& internal_key, const Slice& user_key,
|
||||
const size_t ts_sz) {
|
||||
if (ts_sz) {
|
||||
static constexpr char kTsMax[] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
if (ts_sz <= strlen(kTsMax)) {
|
||||
internal_key = InternalKey(user_key, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion, Slice(kTsMax, ts_sz));
|
||||
} else {
|
||||
internal_key =
|
||||
InternalKey(user_key, kMaxSequenceNumber, kTypeRangeDeletion,
|
||||
std::string(ts_sz, '\xff'));
|
||||
}
|
||||
} else {
|
||||
internal_key.Set(user_key, kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Status CompactionOutputs::AddRangeDels(
|
||||
const Slice* comp_start_user_key, const Slice* comp_end_user_key,
|
||||
CompactionIterationStats& range_del_out_stats, bool bottommost_level,
|
||||
const InternalKeyComparator& icmp, SequenceNumber earliest_snapshot,
|
||||
const Slice& next_table_min_key, const std::string& full_history_ts_low) {
|
||||
// The following example does not happen since
|
||||
// CompactionOutput::ShouldStopBefore() always return false for the first
|
||||
// point key. But we should consider removing this dependency. Suppose for the
|
||||
// first compaction output file,
|
||||
// - next_table_min_key.user_key == comp_start_user_key
|
||||
// - no point key is in the output file
|
||||
// - there is a range tombstone @seqno to be added that covers
|
||||
// comp_start_user_key
|
||||
// Then meta.smallest will be set to comp_start_user_key@seqno
|
||||
// and meta.largest will be set to comp_start_user_key@kMaxSequenceNumber
|
||||
// which violates the assumption that meta.smallest should be <= meta.largest.
|
||||
assert(HasRangeDel());
|
||||
FileMetaData& meta = current_output().meta;
|
||||
const Comparator* ucmp = icmp.user_comparator();
|
||||
InternalKey lower_bound_buf, upper_bound_buf;
|
||||
|
||||
Slice lower_bound_guard, upper_bound_guard;
|
||||
std::string smallest_user_key;
|
||||
const Slice *lower_bound, *upper_bound;
|
||||
bool lower_bound_from_sub_compact = false;
|
||||
|
||||
// We first determine the internal key lower_bound and upper_bound for
|
||||
// this output file. All and only range tombstones that overlap with
|
||||
// [lower_bound, upper_bound] should be added to this file. File
|
||||
// boundaries (meta.smallest/largest) should be updated accordingly when
|
||||
// extended by range tombstones.
|
||||
size_t output_size = outputs_.size();
|
||||
if (output_size == 1) {
|
||||
// This is the first file in the subcompaction.
|
||||
//
|
||||
// When outputting a range tombstone that spans a subcompaction boundary,
|
||||
// the files on either side of that boundary need to include that
|
||||
// boundary's user key. Otherwise, the spanning range tombstone would lose
|
||||
// coverage.
|
||||
//
|
||||
// To achieve this while preventing files from overlapping in internal key
|
||||
// (an LSM invariant violation), we allow the earlier file to include the
|
||||
// boundary user key up to `kMaxSequenceNumber,kTypeRangeDeletion`. The
|
||||
// later file can begin at the boundary user key at the newest key version
|
||||
// it contains. At this point that version number is unknown since we have
|
||||
// not processed the range tombstones yet, so permit any version. Same story
|
||||
// applies to timestamp, and a non-nullptr `comp_start_user_key` should have
|
||||
// `kMaxTs` here, which similarly permits any timestamp.
|
||||
if (comp_start_user_key) {
|
||||
lower_bound_buf.Set(*comp_start_user_key, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion);
|
||||
lower_bound_guard = lower_bound_buf.Encode();
|
||||
lower_bound = &lower_bound_guard;
|
||||
} else {
|
||||
lower_bound = nullptr;
|
||||
}
|
||||
} else {
|
||||
// For the first output table, include range tombstones before the min
|
||||
// key but after the subcompaction boundary.
|
||||
lower_bound = comp_start_user_key;
|
||||
lower_bound_from_sub_compact = true;
|
||||
} else if (meta.smallest.size() > 0) {
|
||||
// For subsequent output tables, only include range tombstones from min
|
||||
// key onwards since the previous file was extended to contain range
|
||||
// tombstones falling before min key.
|
||||
if (range_tombstone_lower_bound_.size() > 0) {
|
||||
assert(meta.smallest.size() == 0 ||
|
||||
icmp.Compare(range_tombstone_lower_bound_, meta.smallest) < 0);
|
||||
lower_bound_guard = range_tombstone_lower_bound_.Encode();
|
||||
} else {
|
||||
assert(meta.smallest.size() > 0);
|
||||
lower_bound_guard = meta.smallest.Encode();
|
||||
}
|
||||
smallest_user_key = meta.smallest.user_key().ToString(false /*hex*/);
|
||||
lower_bound_guard = Slice(smallest_user_key);
|
||||
lower_bound = &lower_bound_guard;
|
||||
} else {
|
||||
lower_bound = nullptr;
|
||||
}
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
if (next_table_min_key.empty()) {
|
||||
// Last file of the subcompaction.
|
||||
if (comp_end_user_key) {
|
||||
upper_bound_buf.Set(*comp_end_user_key, kMaxSequenceNumber,
|
||||
kTypeRangeDeletion);
|
||||
upper_bound_guard = upper_bound_buf.Encode();
|
||||
upper_bound = &upper_bound_guard;
|
||||
if (!next_table_min_key.empty()) {
|
||||
// This may be the last file in the subcompaction in some cases, so we
|
||||
// need to compare the end key of subcompaction with the next file start
|
||||
// key. When the end key is chosen by the subcompaction, we know that
|
||||
// it must be the biggest key in output file. Therefore, it is safe to
|
||||
// use the smaller key as the upper bound of the output file, to ensure
|
||||
// that there is no overlapping between different output files.
|
||||
upper_bound_guard = ExtractUserKey(next_table_min_key);
|
||||
if (comp_end_user_key != nullptr &&
|
||||
ucmp->CompareWithoutTimestamp(upper_bound_guard, *comp_end_user_key) >=
|
||||
0) {
|
||||
upper_bound = comp_end_user_key;
|
||||
} else {
|
||||
upper_bound = nullptr;
|
||||
upper_bound = &upper_bound_guard;
|
||||
}
|
||||
} else {
|
||||
// There is another file coming whose coverage will begin at
|
||||
// `next_table_min_key`. The current file needs to extend range tombstone
|
||||
// coverage through its own keys (through `meta.largest`) and through user
|
||||
// keys preceding `next_table_min_key`'s user key.
|
||||
ParsedInternalKey next_table_min_key_parsed;
|
||||
ParseInternalKey(next_table_min_key, &next_table_min_key_parsed,
|
||||
false /* log_err_key */)
|
||||
.PermitUncheckedError();
|
||||
assert(next_table_min_key_parsed.sequence < kMaxSequenceNumber);
|
||||
assert(meta.largest.size() == 0 ||
|
||||
icmp.Compare(meta.largest.Encode(), next_table_min_key) < 0);
|
||||
assert(!lower_bound || icmp.Compare(*lower_bound, next_table_min_key) <= 0);
|
||||
if (meta.largest.size() > 0 &&
|
||||
ucmp->EqualWithoutTimestamp(meta.largest.user_key(),
|
||||
next_table_min_key_parsed.user_key)) {
|
||||
// Caution: this assumes meta.largest.Encode() lives longer than
|
||||
// upper_bound, which is only true if meta.largest is never updated.
|
||||
// This just happens to be the case here since meta.largest serves
|
||||
// as the upper_bound.
|
||||
upper_bound_guard = meta.largest.Encode();
|
||||
} else {
|
||||
SetMaxSeqAndTs(upper_bound_buf, next_table_min_key_parsed.user_key,
|
||||
ts_sz);
|
||||
upper_bound_guard = upper_bound_buf.Encode();
|
||||
}
|
||||
upper_bound = &upper_bound_guard;
|
||||
// This is the last file in the subcompaction, so extend until the
|
||||
// subcompaction ends.
|
||||
upper_bound = comp_end_user_key;
|
||||
}
|
||||
if (lower_bound && upper_bound &&
|
||||
icmp.Compare(*lower_bound, *upper_bound) > 0) {
|
||||
assert(meta.smallest.size() == 0 &&
|
||||
ucmp->EqualWithoutTimestamp(ExtractUserKey(*lower_bound),
|
||||
ExtractUserKey(*upper_bound)));
|
||||
// This can only happen when lower_bound have the same user key as
|
||||
// next_table_min_key and that there is no point key in the current
|
||||
// compaction output file.
|
||||
return Status::OK();
|
||||
bool has_overlapping_endpoints;
|
||||
if (upper_bound != nullptr && meta.largest.size() > 0) {
|
||||
has_overlapping_endpoints = ucmp->CompareWithoutTimestamp(
|
||||
meta.largest.user_key(), *upper_bound) == 0;
|
||||
} else {
|
||||
has_overlapping_endpoints = false;
|
||||
}
|
||||
|
||||
// The end key of the subcompaction must be bigger or equal to the upper
|
||||
// bound. If the end of subcompaction is null or the upper bound is null,
|
||||
// it means that this file is the last file in the compaction. So there
|
||||
// will be no overlapping between this file and others.
|
||||
assert(comp_end_user_key == nullptr || upper_bound == nullptr ||
|
||||
ucmp->CompareWithoutTimestamp(ExtractUserKey(*upper_bound),
|
||||
*comp_end_user_key) <= 0);
|
||||
auto it = range_del_agg_->NewIterator(lower_bound, upper_bound);
|
||||
Slice last_tombstone_start_user_key{};
|
||||
bool reached_lower_bound = false;
|
||||
const ReadOptions read_options(Env::IOActivity::kCompaction);
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
ucmp->CompareWithoutTimestamp(*upper_bound, *comp_end_user_key) <= 0);
|
||||
auto it = range_del_agg_->NewIterator(lower_bound, upper_bound,
|
||||
has_overlapping_endpoints);
|
||||
// Position the range tombstone output iterator. There may be tombstone
|
||||
// fragments that are entirely out of range, so make sure that we do not
|
||||
// include those.
|
||||
if (lower_bound != nullptr) {
|
||||
it->Seek(*lower_bound);
|
||||
} else {
|
||||
it->SeekToFirst();
|
||||
}
|
||||
for (; it->Valid(); it->Next()) {
|
||||
auto tombstone = it->Tombstone();
|
||||
auto kv = tombstone.Serialize();
|
||||
InternalKey tombstone_end = tombstone.SerializeEndKey();
|
||||
// TODO: the underlying iterator should support clamping the bounds.
|
||||
// tombstone_end.Encode is of form user_key@kMaxSeqno
|
||||
// if it is equal to lower_bound, there is no need to include
|
||||
// such range tombstone.
|
||||
if (!reached_lower_bound && lower_bound &&
|
||||
icmp.Compare(tombstone_end.Encode(), *lower_bound) <= 0) {
|
||||
continue;
|
||||
if (upper_bound != nullptr) {
|
||||
int cmp =
|
||||
ucmp->CompareWithoutTimestamp(*upper_bound, tombstone.start_key_);
|
||||
if ((has_overlapping_endpoints && cmp < 0) ||
|
||||
(!has_overlapping_endpoints && cmp <= 0)) {
|
||||
// Tombstones starting after upper_bound only need to be included in
|
||||
// the next table. If the current SST ends before upper_bound, i.e.,
|
||||
// `has_overlapping_endpoints == false`, we can also skip over range
|
||||
// tombstones that start exactly at upper_bound. Such range
|
||||
// tombstones will be included in the next file and are not relevant
|
||||
// to the point keys or endpoints of the current file.
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(!lower_bound ||
|
||||
icmp.Compare(*lower_bound, tombstone_end.Encode()) <= 0);
|
||||
reached_lower_bound = true;
|
||||
|
||||
const size_t ts_sz = ucmp->timestamp_size();
|
||||
// Garbage collection for range tombstones.
|
||||
// If user-defined timestamp is enabled, range tombstones are dropped if
|
||||
// they are at bottommost_level, below full_history_ts_low and not visible
|
||||
// in any snapshot. trim_ts_ is passed to the constructor for
|
||||
// range_del_agg_, and range_del_agg_ internally drops tombstones above
|
||||
// trim_ts_.
|
||||
bool consider_drop =
|
||||
tombstone.seq_ <= earliest_snapshot &&
|
||||
if (bottommost_level && tombstone.seq_ <= earliest_snapshot &&
|
||||
(ts_sz == 0 ||
|
||||
(!full_history_ts_low.empty() &&
|
||||
ucmp->CompareTimestamp(tombstone.ts_, full_history_ts_low) < 0));
|
||||
if (consider_drop && bottommost_level) {
|
||||
ucmp->CompareTimestamp(tombstone.ts_, full_history_ts_low) < 0))) {
|
||||
// TODO(andrewkr): tombstones that span multiple output files are
|
||||
// counted for each compaction output file, so lots of double
|
||||
// counting.
|
||||
@@ -609,126 +495,96 @@ Status CompactionOutputs::AddRangeDels(
|
||||
continue;
|
||||
}
|
||||
|
||||
auto kv = tombstone.Serialize();
|
||||
assert(lower_bound == nullptr ||
|
||||
ucmp->CompareWithoutTimestamp(ExtractUserKey(*lower_bound),
|
||||
kv.second) < 0);
|
||||
InternalKey tombstone_start = kv.first;
|
||||
if (lower_bound &&
|
||||
ucmp->CompareWithoutTimestamp(tombstone_start.user_key(),
|
||||
ExtractUserKey(*lower_bound)) < 0) {
|
||||
// This just updates the non-timestamp portion of `tombstone_start`'s user
|
||||
// key. Ideally there would be a simpler API usage
|
||||
ParsedInternalKey tombstone_start_parsed;
|
||||
ParseInternalKey(tombstone_start.Encode(), &tombstone_start_parsed,
|
||||
false /* log_err_key */)
|
||||
.PermitUncheckedError();
|
||||
// timestamp should be from where sequence number is from, which is from
|
||||
// tombstone in this case
|
||||
std::string ts =
|
||||
tombstone_start_parsed.GetTimestamp(ucmp->timestamp_size())
|
||||
.ToString();
|
||||
tombstone_start_parsed.user_key = ExtractUserKey(*lower_bound);
|
||||
tombstone_start.SetFrom(tombstone_start_parsed, ts);
|
||||
}
|
||||
if (upper_bound != nullptr &&
|
||||
icmp.Compare(*upper_bound, tombstone_start.Encode()) < 0) {
|
||||
break;
|
||||
}
|
||||
if (lower_bound &&
|
||||
icmp.Compare(tombstone_start.Encode(), *lower_bound) < 0) {
|
||||
tombstone_start.DecodeFrom(*lower_bound);
|
||||
}
|
||||
if (upper_bound && icmp.Compare(*upper_bound, tombstone_end.Encode()) < 0) {
|
||||
tombstone_end.DecodeFrom(*upper_bound);
|
||||
}
|
||||
if (consider_drop && compaction_->KeyRangeNotExistsBeyondOutputLevel(
|
||||
tombstone_start.user_key(),
|
||||
tombstone_end.user_key(), &level_ptrs_)) {
|
||||
range_del_out_stats.num_range_del_drop_obsolete++;
|
||||
range_del_out_stats.num_record_drop_obsolete++;
|
||||
continue;
|
||||
}
|
||||
// Here we show that *only* range tombstones that overlap with
|
||||
// [lower_bound, upper_bound] are added to the current file, and
|
||||
// sanity checking invariants that should hold:
|
||||
// - [tombstone_start, tombstone_end] overlaps with [lower_bound,
|
||||
// upper_bound]
|
||||
// - meta.smallest <= meta.largest
|
||||
// Corresponding assertions are made, the proof is broken is any of them
|
||||
// fails.
|
||||
// TODO: show that *all* range tombstones that overlap with
|
||||
// [lower_bound, upper_bound] are added.
|
||||
// TODO: some invariant about boundaries are correctly updated.
|
||||
//
|
||||
// Note that `tombstone_start` is updated in the if condition above, we use
|
||||
// tombstone_start to refer to its initial value, i.e.,
|
||||
// it->Tombstone().first, and use tombstone_start* to refer to its value
|
||||
// after the update.
|
||||
//
|
||||
// To show [lower_bound, upper_bound] overlaps with [tombstone_start,
|
||||
// tombstone_end]:
|
||||
// lower_bound <= upper_bound from the if condition right after all
|
||||
// bounds are initialized. We assume each tombstone fragment has
|
||||
// start_key.user_key < end_key.user_key, so
|
||||
// tombstone_start < tombstone_end by
|
||||
// FragmentedTombstoneIterator::Tombstone(). So these two ranges are both
|
||||
// non-emtpy. The flag `reached_lower_bound` and the if logic before it
|
||||
// ensures lower_bound <= tombstone_end. tombstone_start is only updated
|
||||
// if it has a smaller user_key than lower_bound user_key, so
|
||||
// tombstone_start <= tombstone_start*. The above if condition implies
|
||||
// tombstone_start* <= upper_bound. So we have
|
||||
// tombstone_start <= upper_bound and lower_bound <= tombstone_end
|
||||
// and the two ranges overlap.
|
||||
//
|
||||
// To show meta.smallest <= meta.largest:
|
||||
// From the implementation of UpdateBoundariesForRange(), it suffices to
|
||||
// prove that when it is first called in this function, its parameters
|
||||
// satisfy `start <= end`, where start = max(tombstone_start*, lower_bound)
|
||||
// and end = min(tombstone_end, upper_bound). From the above proof we have
|
||||
// lower_bound <= tombstone_end and lower_bound <= upper_bound. We only need
|
||||
// to show that tombstone_start* <= min(tombstone_end, upper_bound).
|
||||
// Note that tombstone_start*.user_key = max(tombstone_start.user_key,
|
||||
// lower_bound.user_key). Assuming tombstone_end always has
|
||||
// kMaxSequenceNumber and lower_bound.seqno < kMaxSequenceNumber.
|
||||
// Since lower_bound <= tombstone_end and lower_bound.seqno <
|
||||
// tombstone_end.seqno (in absolute number order, not internal key order),
|
||||
// lower_bound.user_key < tombstone_end.user_key.
|
||||
// Since lower_bound.user_key < tombstone_end.user_key and
|
||||
// tombstone_start.user_key < tombstone_end.user_key, tombstone_start* <
|
||||
// tombstone_end. Since tombstone_start* <= upper_bound from the above proof
|
||||
// and tombstone_start* < tombstone_end, tombstone_start* <=
|
||||
// min(tombstone_end, upper_bound), so the two ranges overlap.
|
||||
|
||||
ucmp->CompareWithoutTimestamp(*lower_bound, kv.second) < 0);
|
||||
// Range tombstone is not supported by output validator yet.
|
||||
builder_->Add(kv.first.Encode(), kv.second);
|
||||
assert(icmp.Compare(tombstone_start, tombstone_end) <= 0);
|
||||
meta.UpdateBoundariesForRange(tombstone_start, tombstone_end,
|
||||
tombstone.seq_, icmp);
|
||||
if (!bottommost_level) {
|
||||
bool start_user_key_changed =
|
||||
last_tombstone_start_user_key.empty() ||
|
||||
ucmp->CompareWithoutTimestamp(last_tombstone_start_user_key,
|
||||
it->start_key()) < 0;
|
||||
last_tombstone_start_user_key = it->start_key();
|
||||
if (start_user_key_changed) {
|
||||
// If tombstone_start >= tombstone_end, then either no key range is
|
||||
// covered, or that they have the same user key. If they have the same
|
||||
// user key, then the internal key range should only be within this
|
||||
// level, and no keys from older levels is covered.
|
||||
if (ucmp->CompareWithoutTimestamp(tombstone_start.user_key(),
|
||||
tombstone_end.user_key()) < 0) {
|
||||
SizeApproximationOptions approx_opts;
|
||||
approx_opts.files_size_error_margin = 0.1;
|
||||
auto approximate_covered_size =
|
||||
compaction_->input_version()->version_set()->ApproximateSize(
|
||||
approx_opts, read_options, compaction_->input_version(),
|
||||
tombstone_start.Encode(), tombstone_end.Encode(),
|
||||
compaction_->output_level() + 1 /* start_level */,
|
||||
-1 /* end_level */, kCompaction);
|
||||
meta.compensated_range_deletion_size += approximate_covered_size;
|
||||
InternalKey smallest_candidate = std::move(kv.first);
|
||||
if (lower_bound != nullptr &&
|
||||
ucmp->CompareWithoutTimestamp(smallest_candidate.user_key(),
|
||||
*lower_bound) <= 0) {
|
||||
// Pretend the smallest key has the same user key as lower_bound
|
||||
// (the max key in the previous table or subcompaction) in order for
|
||||
// files to appear key-space partitioned.
|
||||
//
|
||||
// When lower_bound is chosen by a subcompaction, we know that
|
||||
// subcompactions over smaller keys cannot contain any keys at
|
||||
// lower_bound. We also know that smaller subcompactions exist,
|
||||
// because otherwise the subcompaction woud be unbounded on the left.
|
||||
// As a result, we know that no other files on the output level will
|
||||
// contain actual keys at lower_bound (an output file may have a
|
||||
// largest key of lower_bound@kMaxSequenceNumber, but this only
|
||||
// indicates a large range tombstone was truncated). Therefore, it is
|
||||
// safe to use the tombstone's sequence number, to ensure that keys at
|
||||
// lower_bound at lower levels are covered by truncated tombstones.
|
||||
//
|
||||
// If lower_bound was chosen by the smallest data key in the file,
|
||||
// choose lowest seqnum so this file's smallest internal key comes
|
||||
// after the previous file's largest. The fake seqnum is OK because
|
||||
// the read path's file-picking code only considers user key.
|
||||
if (lower_bound_from_sub_compact) {
|
||||
if (ts_sz) {
|
||||
assert(tombstone.ts_.size() == ts_sz);
|
||||
smallest_candidate = InternalKey(*lower_bound, tombstone.seq_,
|
||||
kTypeRangeDeletion, tombstone.ts_);
|
||||
} else {
|
||||
smallest_candidate =
|
||||
InternalKey(*lower_bound, tombstone.seq_, kTypeRangeDeletion);
|
||||
}
|
||||
} else {
|
||||
smallest_candidate = InternalKey(*lower_bound, 0, kTypeRangeDeletion);
|
||||
}
|
||||
}
|
||||
InternalKey largest_candidate = tombstone.SerializeEndKey();
|
||||
if (upper_bound != nullptr &&
|
||||
ucmp->CompareWithoutTimestamp(*upper_bound,
|
||||
largest_candidate.user_key()) <= 0) {
|
||||
// Pretend the largest key has the same user key as upper_bound (the
|
||||
// min key in the following table or subcompaction) in order for files
|
||||
// to appear key-space partitioned.
|
||||
//
|
||||
// Choose highest seqnum so this file's largest internal key comes
|
||||
// before the next file's/subcompaction's smallest. The fake seqnum is
|
||||
// OK because the read path's file-picking code only considers the
|
||||
// user key portion.
|
||||
//
|
||||
// Note Seek() also creates InternalKey with (user_key,
|
||||
// kMaxSequenceNumber), but with kTypeDeletion (0x7) instead of
|
||||
// kTypeRangeDeletion (0xF), so the range tombstone comes before the
|
||||
// Seek() key in InternalKey's ordering. So Seek() will look in the
|
||||
// next file for the user key
|
||||
if (ts_sz) {
|
||||
static constexpr char kTsMax[] = "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
if (ts_sz <= strlen(kTsMax)) {
|
||||
largest_candidate =
|
||||
InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion,
|
||||
Slice(kTsMax, ts_sz));
|
||||
} else {
|
||||
largest_candidate =
|
||||
InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion,
|
||||
std::string(ts_sz, '\xff'));
|
||||
}
|
||||
} else {
|
||||
largest_candidate =
|
||||
InternalKey(*upper_bound, kMaxSequenceNumber, kTypeRangeDeletion);
|
||||
}
|
||||
}
|
||||
#ifndef NDEBUG
|
||||
SequenceNumber smallest_ikey_seqnum = kMaxSequenceNumber;
|
||||
if (meta.smallest.size() > 0) {
|
||||
smallest_ikey_seqnum = GetInternalKeySeqno(meta.smallest.Encode());
|
||||
}
|
||||
#endif
|
||||
meta.UpdateBoundariesForRange(smallest_candidate, largest_candidate,
|
||||
tombstone.seq_, icmp);
|
||||
// The smallest key in a file is used for range tombstone truncation, so
|
||||
// it cannot have a seqnum of 0 (unless the smallest data key in a file
|
||||
// has a seqnum of 0). Otherwise, the truncated tombstone may expose
|
||||
// deleted keys at lower levels.
|
||||
assert(smallest_ikey_seqnum == 0 ||
|
||||
ExtractInternalKeyFooter(meta.smallest.Encode()) !=
|
||||
PackSequenceAndType(0, kTypeRangeDeletion));
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -785,8 +641,6 @@ CompactionOutputs::CompactionOutputs(const Compaction* compaction,
|
||||
if (compaction->output_level() != 0) {
|
||||
FillFilesToCutForTtl();
|
||||
}
|
||||
|
||||
level_ptrs_ = std::vector<size_t>(compaction_->number_levels(), 0);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -167,15 +167,9 @@ class CompactionOutputs {
|
||||
current_output_file_size_ = 0;
|
||||
}
|
||||
|
||||
// Add range deletions from the range_del_agg_ to the current output file.
|
||||
// Input parameters, `range_tombstone_lower_bound_` and current output's
|
||||
// metadata determine the bounds on range deletions to add. Updates output
|
||||
// file metadata boundary if extended by range tombstones.
|
||||
//
|
||||
// Add range-dels from the aggregator to the current output file
|
||||
// @param comp_start_user_key and comp_end_user_key include timestamp if
|
||||
// user-defined timestamp is enabled. Their timestamp should be max timestamp.
|
||||
// @param next_table_min_key internal key lower bound for the next compaction
|
||||
// output.
|
||||
// user-defined timestamp is enabled.
|
||||
// @param full_history_ts_low used for range tombstone garbage collection.
|
||||
Status AddRangeDels(const Slice* comp_start_user_key,
|
||||
const Slice* comp_end_user_key,
|
||||
@@ -206,10 +200,10 @@ class CompactionOutputs {
|
||||
// We may only split the output when the cursor is in the range. Split
|
||||
if ((!end.has_value() ||
|
||||
icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()), *end) < 0) &&
|
||||
(!start.has_value() ||
|
||||
icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()), *start) > 0)) {
|
||||
ExtractUserKey(output_split_key->Encode()), end.value()) < 0) &&
|
||||
(!start.has_value() || icmp->user_comparator()->Compare(
|
||||
ExtractUserKey(output_split_key->Encode()),
|
||||
start.value()) > 0)) {
|
||||
local_output_split_key_ = output_split_key;
|
||||
}
|
||||
}
|
||||
@@ -227,13 +221,6 @@ class CompactionOutputs {
|
||||
}
|
||||
}
|
||||
|
||||
// Updates states related to file cutting for TTL.
|
||||
// Returns a boolean value indicating whether the current
|
||||
// compaction output file should be cut before `internal_key`.
|
||||
//
|
||||
// @param internal_key the current key to be added to output.
|
||||
bool UpdateFilesToCutForTTLStates(const Slice& internal_key);
|
||||
|
||||
// update tracked grandparents information like grandparent index, if it's
|
||||
// in the gap between 2 grandparent files, accumulated grandparent files size
|
||||
// etc.
|
||||
@@ -320,7 +307,6 @@ class CompactionOutputs {
|
||||
std::unique_ptr<SstPartitioner> partitioner_;
|
||||
|
||||
// A flag determines if this subcompaction has been split by the cursor
|
||||
// for RoundRobin compaction
|
||||
bool is_split_ = false;
|
||||
|
||||
// We also maintain the output split key for each subcompaction to avoid
|
||||
@@ -352,19 +338,6 @@ class CompactionOutputs {
|
||||
// for the current output file, how many file boundaries has it crossed,
|
||||
// basically number of files overlapped * 2
|
||||
size_t grandparent_boundary_switched_num_ = 0;
|
||||
|
||||
// The smallest key of the current output file, this is set when current
|
||||
// output file's smallest key is a range tombstone start key.
|
||||
InternalKey range_tombstone_lower_bound_;
|
||||
|
||||
// Used for calls to compaction->KeyRangeNotExistsBeyondOutputLevel() in
|
||||
// CompactionOutputs::AddRangeDels().
|
||||
// level_ptrs_[i] holds index of the file that was checked during the last
|
||||
// call to compaction->KeyRangeNotExistsBeyondOutputLevel(). This allows
|
||||
// future calls to the function to pick up where it left off, since each
|
||||
// range tombstone added to output file within each subcompaction is in
|
||||
// increasing key range.
|
||||
std::vector<size_t> level_ptrs_;
|
||||
};
|
||||
|
||||
// helper struct to concatenate the last level and penultimate level outputs
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "file/filename.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/statistics_impl.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -31,15 +31,27 @@ bool FindIntraL0Compaction(const std::vector<FileMetaData*>& level_files,
|
||||
size_t min_files_to_compact,
|
||||
uint64_t max_compact_bytes_per_del_file,
|
||||
uint64_t max_compaction_bytes,
|
||||
CompactionInputFiles* comp_inputs) {
|
||||
CompactionInputFiles* comp_inputs,
|
||||
SequenceNumber earliest_mem_seqno) {
|
||||
// Do not pick ingested file when there is at least one memtable not flushed
|
||||
// which of seqno is overlap with the sst.
|
||||
TEST_SYNC_POINT("FindIntraL0Compaction");
|
||||
|
||||
size_t start = 0;
|
||||
|
||||
if (level_files.size() == 0 || level_files[start]->being_compacted) {
|
||||
for (; start < level_files.size(); start++) {
|
||||
if (level_files[start]->being_compacted) {
|
||||
return false;
|
||||
}
|
||||
// If there is no data in memtable, the earliest sequence number would the
|
||||
// largest sequence number in last memtable.
|
||||
// Because all files are sorted in descending order by largest_seqno, so we
|
||||
// only need to check the first one.
|
||||
if (level_files[start]->fd.largest_seqno <= earliest_mem_seqno) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (start >= level_files.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t compact_bytes = static_cast<size_t>(level_files[start]->fd.file_size);
|
||||
size_t compact_bytes_per_del_file = std::numeric_limits<size_t>::max();
|
||||
// Compaction range will be [start, limit).
|
||||
@@ -611,21 +623,23 @@ 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);
|
||||
// DBImpl::CompactRange() set output level to be the last level
|
||||
assert(output_level == max_output_level);
|
||||
if (ioptions_.allow_ingest_behind) {
|
||||
assert(output_level == vstorage->num_levels() - 2);
|
||||
} else {
|
||||
assert(output_level == vstorage->num_levels() - 1);
|
||||
}
|
||||
// DBImpl::RunManualCompaction will make full range for universal compaction
|
||||
assert(begin == nullptr);
|
||||
assert(end == nullptr);
|
||||
*compaction_end = nullptr;
|
||||
|
||||
int start_level = 0;
|
||||
for (; start_level <= max_output_level &&
|
||||
for (; start_level < vstorage->num_levels() &&
|
||||
vstorage->NumLevelFiles(start_level) == 0;
|
||||
start_level++) {
|
||||
}
|
||||
if (start_level > max_output_level) {
|
||||
if (start_level == vstorage->num_levels()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -635,9 +649,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(max_output_level + 1 -
|
||||
std::vector<CompactionInputFiles> inputs(vstorage->num_levels() -
|
||||
start_level);
|
||||
for (int level = start_level; level <= max_output_level; level++) {
|
||||
for (int level = start_level; level < vstorage->num_levels(); level++) {
|
||||
inputs[level - start_level].level = level;
|
||||
auto& files = inputs[level - start_level].files;
|
||||
for (FileMetaData* f : vstorage->LevelFiles(level)) {
|
||||
@@ -751,10 +765,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
|
||||
// for BOTTOM LEVEL compaction only, use max_file_num_to_ignore to filter out
|
||||
// files that are created during the current compaction.
|
||||
if ((compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kForceOptimized ||
|
||||
compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kIfHaveCompactionFilter) &&
|
||||
if (compact_range_options.bottommost_level_compaction ==
|
||||
BottommostLevelCompaction::kForceOptimized &&
|
||||
max_file_num_to_ignore != std::numeric_limits<uint64_t>::max()) {
|
||||
assert(input_level == output_level);
|
||||
// inputs_shrunk holds a continuous subset of input files which were all
|
||||
@@ -877,6 +889,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
return compaction;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
// Test whether two files have overlapping key-ranges.
|
||||
bool HaveOverlappingKeyRanges(const Comparator* c, const SstFileMetaData& a,
|
||||
@@ -982,7 +995,6 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
current_files[f].name +
|
||||
" is currently being compacted.");
|
||||
}
|
||||
|
||||
input_files->insert(TableFileNameToNumber(current_files[f].name));
|
||||
}
|
||||
|
||||
@@ -1115,6 +1127,7 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void CompactionPicker::RegisterCompaction(Compaction* c) {
|
||||
if (c == nullptr) {
|
||||
@@ -1124,11 +1137,7 @@ void CompactionPicker::RegisterCompaction(Compaction* c) {
|
||||
c->output_level() == 0 ||
|
||||
!FilesRangeOverlapWithCompaction(*c->inputs(), c->output_level(),
|
||||
c->GetPenultimateLevel()));
|
||||
// CompactionReason::kExternalSstIngestion's start level is just a placeholder
|
||||
// number without actual meaning as file ingestion technically does not have
|
||||
// an input level like other compactions
|
||||
if ((c->start_level() == 0 &&
|
||||
c->compaction_reason() != CompactionReason::kExternalSstIngestion) ||
|
||||
if (c->start_level() == 0 ||
|
||||
ioptions_.compaction_style == kCompactionStyleUniversal) {
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
}
|
||||
|
||||
@@ -51,15 +51,14 @@ class CompactionPicker {
|
||||
virtual ~CompactionPicker();
|
||||
|
||||
// Pick level and inputs for a new compaction.
|
||||
//
|
||||
// Returns nullptr if there is no compaction to be done.
|
||||
// Otherwise returns a pointer to a heap-allocated object that
|
||||
// describes the compaction. Caller should delete the result.
|
||||
virtual Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) = 0;
|
||||
virtual Compaction* PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer,
|
||||
SequenceNumber earliest_memtable_seqno = kMaxSequenceNumber) = 0;
|
||||
|
||||
// Return a compaction object for compacting the range [begin,end] in
|
||||
// the specified level. Returns nullptr if there is nothing in that
|
||||
@@ -92,10 +91,11 @@ class CompactionPicker {
|
||||
// files. If it's not possible to conver an invalid input_files
|
||||
// into a valid one by adding more files, the function will return a
|
||||
// non-ok status with specific reason.
|
||||
//
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status SanitizeCompactionInputFiles(std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Free up the files that participated in a compaction
|
||||
//
|
||||
@@ -227,9 +227,11 @@ class CompactionPicker {
|
||||
|
||||
// A helper function to SanitizeCompactionInputFiles() that
|
||||
// sanitizes "input_files" by adding necessary files.
|
||||
#ifndef ROCKSDB_LITE
|
||||
virtual Status SanitizeCompactionInputFilesForAllLevels(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level) const;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Keeps track of all compactions that are running on Level0.
|
||||
// Protected by DB mutex
|
||||
@@ -242,6 +244,7 @@ class CompactionPicker {
|
||||
const InternalKeyComparator* const icmp_;
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// A dummy compaction that never triggers any automatic
|
||||
// compaction.
|
||||
class NullCompactionPicker : public CompactionPicker {
|
||||
@@ -252,11 +255,12 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
virtual ~NullCompactionPicker() {}
|
||||
|
||||
// Always return "nullptr"
|
||||
Compaction* PickCompaction(const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
VersionStorageInfo* /*vstorage*/,
|
||||
LogBuffer* /* log_buffer */) override {
|
||||
Compaction* PickCompaction(
|
||||
const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const MutableDBOptions& /*mutable_db_options*/,
|
||||
VersionStorageInfo* /*vstorage*/, LogBuffer* /* log_buffer */,
|
||||
SequenceNumber /* earliest_memtable_seqno */) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -282,6 +286,7 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Attempts to find an intra L0 compaction conforming to the given parameters.
|
||||
//
|
||||
@@ -299,11 +304,11 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
// files. Cannot be nullptr.
|
||||
//
|
||||
// @return true iff compaction was found.
|
||||
bool FindIntraL0Compaction(const std::vector<FileMetaData*>& level_files,
|
||||
size_t min_files_to_compact,
|
||||
uint64_t max_compact_bytes_per_del_file,
|
||||
uint64_t max_compaction_bytes,
|
||||
CompactionInputFiles* comp_inputs);
|
||||
bool FindIntraL0Compaction(
|
||||
const std::vector<FileMetaData*>& level_files, size_t min_files_to_compact,
|
||||
uint64_t max_compact_bytes_per_del_file, uint64_t max_compaction_bytes,
|
||||
CompactionInputFiles* comp_inputs,
|
||||
SequenceNumber earliest_mem_seqno = kMaxSequenceNumber);
|
||||
|
||||
CompressionType GetCompressionType(const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/compaction/compaction_picker_fifo.h"
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
@@ -16,7 +17,6 @@
|
||||
#include "db/column_family.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -232,8 +232,7 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (total_size >
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
|
||||
} else {
|
||||
// If the last level is non-L0, we actually don't know which file is
|
||||
// logically the oldest since the file creation time only represents
|
||||
// when this file was compacted to this level, which is independent
|
||||
@@ -248,27 +247,15 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
inputs[0].files.push_back(f);
|
||||
char tmp_fsize[16];
|
||||
AppendHumanBytes(f->fd.GetFileSize(), tmp_fsize, sizeof(tmp_fsize));
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with size %s for deletion under total size %" PRIu64
|
||||
" vs max table files size %" PRIu64,
|
||||
cf_name.c_str(), f->fd.GetNumber(), tmp_fsize, total_size,
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size);
|
||||
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with size %s for deletion",
|
||||
cf_name.c_str(), f->fd.GetNumber(), tmp_fsize);
|
||||
if (total_size <=
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: nothing to do. Total size %" PRIu64
|
||||
", max size %" PRIu64 "\n",
|
||||
cf_name.c_str(), total_size,
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
@@ -285,36 +272,31 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
Compaction* FIFOCompactionPicker::PickCompactionToWarm(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
const std::vector<FileTemperatureAge>& ages =
|
||||
mutable_cf_options.compaction_options_fifo
|
||||
.file_temperature_age_thresholds;
|
||||
if (ages.empty()) {
|
||||
if (mutable_cf_options.compaction_options_fifo.age_for_warm == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Does not apply to multi-level FIFO.
|
||||
if (vstorage->num_levels() > 1) {
|
||||
return nullptr;
|
||||
// PickCompactionToWarm is only triggered if there is no non-L0 files.
|
||||
for (int level = 1; level < vstorage->num_levels(); ++level) {
|
||||
if (GetTotalFilesSize(vstorage->LevelFiles(level)) > 0) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
const int kLevel0 = 0;
|
||||
const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(kLevel0);
|
||||
if (level_files.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int64_t _current_time;
|
||||
auto status = ioptions_.clock->GetCurrentTime(&_current_time);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: Couldn't get current time: %s. "
|
||||
"Not doing compactions based on file temperature-age threshold. ",
|
||||
cf_name.c_str(), status.ToString().c_str());
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: Couldn't get current time: %s. "
|
||||
"Not doing compactions based on warm threshold. ",
|
||||
cf_name.c_str(), status.ToString().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
const uint64_t current_time = static_cast<uint64_t>(_current_time);
|
||||
@@ -333,77 +315,56 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
inputs[0].level = 0;
|
||||
|
||||
// avoid underflow
|
||||
uint64_t min_age = ages[0].age;
|
||||
// kLastTemperature means target temperature is to be determined.
|
||||
Temperature compaction_target_temp = Temperature::kLastTemperature;
|
||||
if (current_time > min_age) {
|
||||
uint64_t create_time_threshold = current_time - min_age;
|
||||
if (current_time > mutable_cf_options.compaction_options_fifo.age_for_warm) {
|
||||
uint64_t create_time_threshold =
|
||||
current_time - mutable_cf_options.compaction_options_fifo.age_for_warm;
|
||||
uint64_t compaction_size = 0;
|
||||
// We will ideally identify a file qualifying for temperature change by
|
||||
// knowing the timestamp for the youngest entry in the file. However, right
|
||||
// now we don't have the information. We infer it by looking at timestamp of
|
||||
// the previous file's (which is just younger) oldest entry's timestamp.
|
||||
Temperature cur_target_temp;
|
||||
// avoid index underflow
|
||||
assert(level_files.size() >= 1);
|
||||
for (size_t index = level_files.size() - 1; index >= 1; --index) {
|
||||
// Try to add cur_file to compaction inputs.
|
||||
FileMetaData* cur_file = level_files[index];
|
||||
// prev_file is just younger than cur_file
|
||||
FileMetaData* prev_file = level_files[index - 1];
|
||||
if (cur_file->being_compacted) {
|
||||
// Should not happen since we check for
|
||||
// `level0_compactions_in_progress_` above. Here we simply just don't
|
||||
// schedule anything.
|
||||
// We will ideally identify a file qualifying for warm tier by knowing
|
||||
// the timestamp for the youngest entry in the file. However, right now
|
||||
// we don't have the information. We infer it by looking at timestamp
|
||||
// of the next file's (which is just younger) oldest entry's timestamp.
|
||||
FileMetaData* prev_file = nullptr;
|
||||
for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
|
||||
FileMetaData* f = *ritr;
|
||||
assert(f);
|
||||
if (f->being_compacted) {
|
||||
// Right now this probably won't happen as we never try to schedule
|
||||
// two compactions in parallel, so here we just simply don't schedule
|
||||
// anything.
|
||||
return nullptr;
|
||||
}
|
||||
uint64_t oldest_ancestor_time = prev_file->TryGetOldestAncesterTime();
|
||||
if (oldest_ancestor_time == kUnknownOldestAncesterTime) {
|
||||
uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time == kUnknownOldestAncesterTime) {
|
||||
// Older files might not have enough information. It is possible to
|
||||
// handle these files by looking at newer files, but maintaining the
|
||||
// logic isn't worth it.
|
||||
break;
|
||||
}
|
||||
if (oldest_ancestor_time > create_time_threshold) {
|
||||
// cur_file is too fresh
|
||||
if (oldest_ancester_time > create_time_threshold) {
|
||||
// The previous file (which has slightly older data) doesn't qualify
|
||||
// for warm tier.
|
||||
break;
|
||||
}
|
||||
cur_target_temp = ages[0].temperature;
|
||||
for (size_t i = 1; i < ages.size(); ++i) {
|
||||
if (current_time >= ages[i].age &&
|
||||
oldest_ancestor_time <= current_time - ages[i].age) {
|
||||
cur_target_temp = ages[i].temperature;
|
||||
}
|
||||
}
|
||||
if (cur_file->temperature == cur_target_temp) {
|
||||
if (inputs[0].empty()) {
|
||||
continue;
|
||||
} else {
|
||||
if (prev_file != nullptr) {
|
||||
compaction_size += prev_file->fd.GetFileSize();
|
||||
if (compaction_size > mutable_cf_options.max_compaction_bytes) {
|
||||
break;
|
||||
}
|
||||
inputs[0].files.push_back(prev_file);
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with next file's oldest time %" PRIu64 " for warm",
|
||||
cf_name.c_str(), prev_file->fd.GetNumber(),
|
||||
oldest_ancester_time);
|
||||
}
|
||||
|
||||
// cur_file needs to change temperature
|
||||
if (compaction_target_temp == Temperature::kLastTemperature) {
|
||||
assert(inputs[0].empty());
|
||||
compaction_target_temp = cur_target_temp;
|
||||
} else if (cur_target_temp != compaction_target_temp) {
|
||||
assert(!inputs[0].empty());
|
||||
break;
|
||||
}
|
||||
if (inputs[0].empty() || compaction_size + cur_file->fd.GetFileSize() <=
|
||||
mutable_cf_options.max_compaction_bytes) {
|
||||
inputs[0].files.push_back(cur_file);
|
||||
compaction_size += cur_file->fd.GetFileSize();
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with next file's oldest time %" PRIu64 " for temperature %s.",
|
||||
cf_name.c_str(), cur_file->fd.GetNumber(), oldest_ancestor_time,
|
||||
temperature_to_string[cur_target_temp].c_str());
|
||||
}
|
||||
if (compaction_size > mutable_cf_options.max_compaction_bytes) {
|
||||
if (f->temperature == Temperature::kUnknown ||
|
||||
f->temperature == Temperature::kHot) {
|
||||
prev_file = f;
|
||||
} else if (!inputs[0].files.empty()) {
|
||||
// A warm file newer than files picked.
|
||||
break;
|
||||
} else {
|
||||
assert(prev_file == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -417,7 +378,7 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
std::move(inputs), 0, 0 /* output file size limit */,
|
||||
0 /* max compaction bytes, not applicable */, 0 /* output path ID */,
|
||||
mutable_cf_options.compression, mutable_cf_options.compression_opts,
|
||||
compaction_target_temp,
|
||||
Temperature::kWarm,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false, /* trim_ts */ "",
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false, /* l0_files_might_overlap */ true,
|
||||
@@ -428,7 +389,7 @@ Compaction* FIFOCompactionPicker::PickTemperatureChangeCompaction(
|
||||
Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
LogBuffer* log_buffer, SequenceNumber /*earliest_memtable_seqno*/) {
|
||||
Compaction* c = nullptr;
|
||||
if (mutable_cf_options.ttl > 0) {
|
||||
c = PickTTLCompaction(cf_name, mutable_cf_options, mutable_db_options,
|
||||
@@ -439,8 +400,8 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
vstorage, log_buffer);
|
||||
}
|
||||
if (c == nullptr) {
|
||||
c = PickTemperatureChangeCompaction(
|
||||
cf_name, mutable_cf_options, mutable_db_options, vstorage, log_buffer);
|
||||
c = PickCompactionToWarm(cf_name, mutable_cf_options, mutable_db_options,
|
||||
vstorage, log_buffer);
|
||||
}
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
@@ -469,3 +430,4 @@ Compaction* FIFOCompactionPicker::CompactRange(
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // !ROCKSDB_LITE
|
||||
Loaded 100 of 1199 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user