mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d57a393a8 | ||
|
|
224da7f494 | ||
|
|
9674b843c2 | ||
|
|
7448618ac8 | ||
|
|
52626f10b1 | ||
|
|
7c25388623 |
No files matched your search
Executable
+54
@@ -0,0 +1,54 @@
|
||||
#! /bin/bash
|
||||
|
||||
# Work around issue with parallel make output causing random error, as in
|
||||
# make[1]: write error: stdout
|
||||
# Probably due to a kernel bug:
|
||||
# https://bugs.launchpad.net/ubuntu/+source/linux-signed/+bug/1814393
|
||||
# Seems to affect image ubuntu-1604:201903-01 and ubuntu-1604:202004-01
|
||||
|
||||
cd "$(dirname $0)"
|
||||
|
||||
if [ ! -x cat_ignore_eagain.out ]; then
|
||||
cc -x c -o cat_ignore_eagain.out - << EOF
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
int main() {
|
||||
int n, m, p;
|
||||
char buf[1024];
|
||||
for (;;) {
|
||||
n = read(STDIN_FILENO, buf, 1024);
|
||||
if (n > 0 && n <= 1024) {
|
||||
for (m = 0; m < n;) {
|
||||
p = write(STDOUT_FILENO, buf + m, n - m);
|
||||
if (p < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
// ignore but pause a bit
|
||||
usleep(100);
|
||||
} else {
|
||||
perror("write failed");
|
||||
return 42;
|
||||
}
|
||||
} else {
|
||||
m += p;
|
||||
}
|
||||
}
|
||||
} else if (n < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
// ignore but pause a bit
|
||||
usleep(100);
|
||||
} else {
|
||||
// Some non-ignorable error
|
||||
perror("read failed");
|
||||
return 43;
|
||||
}
|
||||
} else {
|
||||
// EOF
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
exec ./cat_ignore_eagain.out
|
||||
+361
-450
File diff suppressed because it is too large.
Load diff
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Download 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
|
||||
args: https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py
|
||||
|
||||
- name: Check format
|
||||
run: VERBOSE_CHECK=1 make check-format
|
||||
|
||||
+2
-1
@@ -36,6 +36,7 @@ manifest_dump
|
||||
sst_dump
|
||||
blob_dump
|
||||
block_cache_trace_analyzer
|
||||
db_with_timestamp_basic_test
|
||||
tools/block_cache_analyzer/*.pyc
|
||||
column_aware_encoding_exp
|
||||
util/build_version.cc
|
||||
@@ -51,6 +52,7 @@ rocksdb_dump
|
||||
rocksdb_undump
|
||||
db_test2
|
||||
trace_analyzer
|
||||
trace_analyzer_test
|
||||
block_cache_trace_analyzer
|
||||
io_tracer_parser
|
||||
.DS_Store
|
||||
@@ -93,4 +95,3 @@ fuzz/proto/gen/
|
||||
fuzz/crash-*
|
||||
|
||||
cmake-build-*
|
||||
third-party/folly/
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
dist: xenial
|
||||
language: cpp
|
||||
os:
|
||||
- linux
|
||||
arch:
|
||||
- arm64
|
||||
- ppc64le
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
cache:
|
||||
- ccache
|
||||
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- libgflags-dev
|
||||
- libbz2-dev
|
||||
- liblz4-dev
|
||||
- libsnappy-dev
|
||||
- liblzma-dev # xv
|
||||
- libzstd-dev
|
||||
- zlib1g-dev
|
||||
|
||||
env:
|
||||
- TEST_GROUP=platform_dependent # 16-18 minutes
|
||||
- TEST_GROUP=1 # 33-35 minutes
|
||||
- TEST_GROUP=2 # 18-20 minutes
|
||||
- TEST_GROUP=3 # 20-22 minutes
|
||||
- TEST_GROUP=4 # 12-14 minutes
|
||||
# Run java tests
|
||||
- JOB_NAME=java_test # 4-11 minutes
|
||||
# Build ROCKSDB_LITE
|
||||
- JOB_NAME=lite_build # 3-4 minutes
|
||||
# Build examples
|
||||
- JOB_NAME=examples # 5-7 minutes
|
||||
- JOB_NAME=cmake # 3-5 minutes
|
||||
- JOB_NAME=cmake-gcc8 # 3-5 minutes
|
||||
- JOB_NAME=cmake-gcc9 # 3-5 minutes
|
||||
- JOB_NAME=cmake-gcc9-c++20 # 3-5 minutes
|
||||
- JOB_NAME=cmake-mingw # 3 minutes
|
||||
- JOB_NAME=make-gcc4.8
|
||||
- JOB_NAME=status_checked
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=make-gcc4.8
|
||||
- os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=make-gcc4.8
|
||||
- os: linux
|
||||
compiler: clang
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=platform_dependent
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/ AND commit_message !~ /java/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/ AND commit_message !~ /java/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake-gcc9
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc9
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake-gcc9-c++20
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc9-c++20
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=status_checked
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=status_checked
|
||||
|
||||
install:
|
||||
- if [ "${JOB_NAME}" == cmake-gcc8 ]; then
|
||||
sudo apt-get install -y g++-8 || exit $?;
|
||||
CC=gcc-8 && CXX=g++-8;
|
||||
fi
|
||||
- if [ "${JOB_NAME}" == cmake-gcc9 ] || [ "${JOB_NAME}" == cmake-gcc9-c++20 ]; then
|
||||
sudo apt-get install -y g++-9 || exit $?;
|
||||
CC=gcc-9 && CXX=g++-9;
|
||||
fi
|
||||
- if [ "${JOB_NAME}" == cmake-mingw ]; then
|
||||
sudo apt-get install -y mingw-w64 || exit $?;
|
||||
fi
|
||||
- if [ "${JOB_NAME}" == make-gcc4.8 ]; then
|
||||
sudo apt-get install -y g++-4.8 || exit $?;
|
||||
CC=gcc-4.8 && CXX=g++-4.8;
|
||||
fi
|
||||
- |
|
||||
if [[ "${JOB_NAME}" == cmake* ]]; then
|
||||
sudo apt-get remove -y cmake cmake-data
|
||||
export CMAKE_DEB="cmake-3.14.5-Linux-$(uname -m).deb"
|
||||
export CMAKE_DEB_URL="https://rocksdb-deps.s3-us-west-2.amazonaws.com/cmake/${CMAKE_DEB}"
|
||||
curl --silent --fail --show-error --location --output "${CMAKE_DEB}" "${CMAKE_DEB_URL}" || exit $?
|
||||
sudo dpkg -i "${CMAKE_DEB}" || exit $?
|
||||
which cmake && cmake --version
|
||||
fi
|
||||
- |
|
||||
if [[ "${JOB_NAME}" == java_test || "${JOB_NAME}" == cmake* ]]; then
|
||||
# Ensure JDK 8
|
||||
sudo apt-get install -y openjdk-8-jdk || exit $?
|
||||
export PATH=/usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)/bin:$PATH
|
||||
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
fi
|
||||
|
||||
before_script:
|
||||
# Increase the maximum number of open file descriptors, since some tests use
|
||||
# more FDs than the default limit.
|
||||
- ulimit -n 8192
|
||||
|
||||
script:
|
||||
- date; ${CXX} --version
|
||||
- if [ `command -v ccache` ]; then ccache -C; fi
|
||||
- case $TEST_GROUP in
|
||||
platform_dependent)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=only make -j4 all_but_some_tests check_some
|
||||
;;
|
||||
1)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_END=backupable_db_test make -j4 check_some
|
||||
;;
|
||||
2)
|
||||
OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" LIB_MODE=shared V=1 make -j4 tools && OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=backupable_db_test ROCKSDBTESTS_END=db_universal_compaction_test make -j4 check_some
|
||||
;;
|
||||
3)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=db_universal_compaction_test ROCKSDBTESTS_END=table_properties_collector_test make -j4 check_some
|
||||
;;
|
||||
4)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=table_properties_collector_test make -j4 check_some
|
||||
;;
|
||||
esac
|
||||
- case $JOB_NAME in
|
||||
java_test)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 make rocksdbjava jtest
|
||||
;;
|
||||
lite_build)
|
||||
OPT='-DTRAVIS -DROCKSDB_LITE' LIB_MODE=shared V=1 make -j4 all
|
||||
;;
|
||||
examples)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 make -j4 static_lib && cd examples && make -j4
|
||||
;;
|
||||
cmake-mingw)
|
||||
sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix;
|
||||
mkdir build && cd build && cmake -DJNI=1 -DWITH_GFLAGS=OFF .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni
|
||||
;;
|
||||
cmake*)
|
||||
case $JOB_NAME in
|
||||
*-c++20)
|
||||
OPT=-DCMAKE_CXX_STANDARD=20
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir build && cd build && cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=0 -DWITH_GFLAGS=0 -DWITH_BENCHMARK_TOOLS=0 -DWITH_TOOLS=0 -DWITH_CORE_TOOLS=1 .. && make -j4 && cd .. && rm -rf build && mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_BUILD_TYPE=Release $OPT && make -j4 rocksdb rocksdbjni
|
||||
;;
|
||||
make-gcc4.8)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 SKIP_LINK=1 make -j4 all && [ "Linking broken because libgflags compiled with newer ABI" ]
|
||||
;;
|
||||
status_checked)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ASSERT_STATUS_CHECKED=1 make -j4 check_some
|
||||
;;
|
||||
esac
|
||||
notifications:
|
||||
email:
|
||||
- leveldb@fb.com
|
||||
+138
-296
@@ -2,7 +2,7 @@
|
||||
# This cmake build is for Windows 64-bit only.
|
||||
#
|
||||
# Prerequisites:
|
||||
# You must have at least Visual Studio 2019. Start the Developer Command Prompt window that is a part of Visual Studio installation.
|
||||
# You must have at least Visual Studio 2015 Update 3. Start the Developer Command Prompt window that is a part of Visual Studio installation.
|
||||
# Run the build commands from within the Developer Command Prompt window to have paths to the compiler and runtime libraries set.
|
||||
# You must have git.exe in your %PATH% environment variable.
|
||||
#
|
||||
@@ -14,7 +14,7 @@
|
||||
# cd build
|
||||
# 3. Run cmake to generate project files for Windows, add more options to enable required third-party libraries.
|
||||
# See thirdparty.inc for more information.
|
||||
# sample command: cmake -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 -DWITH_SNAPPY=1 -DWITH_JEMALLOC=1 -DWITH_JNI=1 ..
|
||||
# sample command: cmake -G "Visual Studio 15 Win64" -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 -DWITH_SNAPPY=1 -DWITH_JEMALLOC=1 -DWITH_JNI=1 ..
|
||||
# 4. Then build the project in debug mode (you may want to add /m[:<N>] flag to run msbuild in <N> parallel threads
|
||||
# or simply /m to use all avail cores)
|
||||
# msbuild rocksdb.sln
|
||||
@@ -27,7 +27,7 @@
|
||||
#
|
||||
# Linux:
|
||||
#
|
||||
# 1. Install a recent toolchain if you're on a older distro. C++17 required (GCC >= 7, Clang >= 5)
|
||||
# 1. Install a recent toolchain such as devtoolset-3 if you're on a older distro. C++11 required.
|
||||
# 2. mkdir build; cd build
|
||||
# 3. cmake ..
|
||||
# 4. make -j
|
||||
@@ -40,8 +40,6 @@ include(GoogleTest)
|
||||
get_rocksdb_version(rocksdb_VERSION)
|
||||
project(rocksdb
|
||||
VERSION ${rocksdb_VERSION}
|
||||
DESCRIPTION "An embeddable persistent key-value store for fast storage"
|
||||
HOMEPAGE_URL https://rocksdb.org/
|
||||
LANGUAGES CXX C ASM)
|
||||
|
||||
if(POLICY CMP0042)
|
||||
@@ -74,15 +72,27 @@ option(WITH_WINDOWS_UTF8_FILENAMES "use UTF8 as characterset for opening files,
|
||||
if (WITH_WINDOWS_UTF8_FILENAMES)
|
||||
add_definitions(-DROCKSDB_WINDOWS_UTF8_FILENAMES)
|
||||
endif()
|
||||
option(ROCKSDB_BUILD_SHARED "Build shared versions of the RocksDB libraries" ON)
|
||||
|
||||
if ($ENV{CIRCLECI})
|
||||
message(STATUS "Build for CircieCI env, a few tests may be disabled")
|
||||
add_definitions(-DCIRCLECI)
|
||||
endif()
|
||||
|
||||
# third-party/folly is only validated to work on Linux and Windows for now.
|
||||
# So only turn it on there by default.
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux|Windows")
|
||||
if(MSVC AND MSVC_VERSION LESS 1910)
|
||||
# Folly does not compile with MSVC older than VS2017
|
||||
option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" OFF)
|
||||
else()
|
||||
option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" ON)
|
||||
endif()
|
||||
else()
|
||||
option(WITH_FOLLY_DISTRIBUTED_MUTEX "build with folly::DistributedMutex" OFF)
|
||||
endif()
|
||||
|
||||
if( NOT DEFINED CMAKE_CXX_STANDARD )
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
endif()
|
||||
|
||||
include(CMakeDependentOption)
|
||||
@@ -90,11 +100,7 @@ include(CMakeDependentOption)
|
||||
if(MSVC)
|
||||
option(WITH_GFLAGS "build with GFlags" OFF)
|
||||
option(WITH_XPRESS "build with windows built in compression" OFF)
|
||||
option(ROCKSDB_SKIP_THIRDPARTY "skip thirdparty.inc" OFF)
|
||||
|
||||
if(NOT ROCKSDB_SKIP_THIRDPARTY)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
endif()
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
else()
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD" AND NOT CMAKE_SYSTEM_NAME MATCHES "kFreeBSD")
|
||||
# FreeBSD has jemalloc as default malloc
|
||||
@@ -176,6 +182,26 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
string(TIMESTAMP TS "%Y-%m-%d %H:%M:%S" UTC)
|
||||
set(BUILD_DATE "${TS}" CACHE STRING "the time we first built rocksdb")
|
||||
|
||||
find_package(Git)
|
||||
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git")
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_SHA COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD )
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE GIT_MOD COMMAND "${GIT_EXECUTABLE}" diff-index HEAD --quiet)
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_DATE COMMAND "${GIT_EXECUTABLE}" log -1 --date=format:"%Y-%m-%d %T" --format="%ad")
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG RESULT_VARIABLE rv COMMAND "${GIT_EXECUTABLE}" symbolic-ref -q --short HEAD OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (rv AND NOT rv EQUAL 0)
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG COMMAND "${GIT_EXECUTABLE}" describe --tags --exact-match OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
endif()
|
||||
else()
|
||||
set(GIT_SHA 0)
|
||||
set(GIT_MOD 1)
|
||||
endif()
|
||||
string(REGEX REPLACE "[^0-9a-fA-F]+" "" GIT_SHA "${GIT_SHA}")
|
||||
string(REGEX REPLACE "[^0-9: /-]+" "" GIT_DATE "${GIT_DATE}")
|
||||
|
||||
option(WITH_MD_LIBRARY "build with MD" ON)
|
||||
if(WIN32 AND MSVC)
|
||||
if(WITH_MD_LIBRARY)
|
||||
@@ -185,17 +211,20 @@ if(WIN32 AND MSVC)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
|
||||
configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)
|
||||
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4800 /wd4996 /wd4351 /wd4100 /wd4204 /wd4324")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing -Wno-invalid-offsetof")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wstrict-prototypes")
|
||||
endif()
|
||||
if(MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format -fno-asynchronous-unwind-tables")
|
||||
add_definitions(-D_POSIX_C_SOURCE=1)
|
||||
endif()
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
@@ -238,13 +267,6 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|AARCH64")
|
||||
endif(HAS_ARMV8_CRC)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|AARCH64")
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "s390x")
|
||||
CHECK_C_COMPILER_FLAG("-march=native" HAS_S390X_MARCH_NATIVE)
|
||||
if(HAS_S390X_MARCH_NATIVE)
|
||||
message(STATUS " HAS_S390X_MARCH_NATIVE yes")
|
||||
endif(HAS_S390X_MARCH_NATIVE)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "s390x")
|
||||
|
||||
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)
|
||||
@@ -270,17 +292,12 @@ if(PORTABLE)
|
||||
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()
|
||||
endif()
|
||||
else()
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
else()
|
||||
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)
|
||||
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64" AND NOT HAS_ARMV8_CRC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
||||
endif()
|
||||
endif()
|
||||
@@ -292,8 +309,7 @@ if(NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
|
||||
if (NOT PORTABLE OR FORCE_SSE42)
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <cstdint>
|
||||
#include <nmmintrin.h>
|
||||
#include <wmmintrin.h>
|
||||
@@ -305,17 +321,16 @@ int main() {
|
||||
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()
|
||||
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()
|
||||
|
||||
# Check if -latomic is required or not
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++17")
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++11")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
std::atomic<uint64_t> x(0);
|
||||
@@ -325,23 +340,44 @@ int main() {
|
||||
return 0;
|
||||
}
|
||||
" BUILTIN_ATOMIC)
|
||||
if (NOT BUILTIN_ATOMIC)
|
||||
#TODO: Check if -latomic exists
|
||||
list(APPEND THIRDPARTY_LIBS atomic)
|
||||
endif()
|
||||
if (NOT BUILTIN_ATOMIC)
|
||||
#TODO: Check if -latomic exists
|
||||
list(APPEND THIRDPARTY_LIBS atomic)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WITH_LIBURING)
|
||||
find_package(uring)
|
||||
if (uring_FOUND)
|
||||
set(CMAKE_REQUIRED_FLAGS "-luring")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <liburing.h>
|
||||
int main() {
|
||||
struct io_uring ring;
|
||||
io_uring_queue_init(1, &ring, 0);
|
||||
return 0;
|
||||
}
|
||||
" HAS_LIBURING)
|
||||
if (HAS_LIBURING)
|
||||
add_definitions(-DROCKSDB_IOURING_PRESENT)
|
||||
list(APPEND THIRDPARTY_LIBS uring::uring)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -luring")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Reset the required flags
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#if defined(_MSC_VER) && !defined(__thread)
|
||||
#define __thread __declspec(thread)
|
||||
#endif
|
||||
int main() {
|
||||
static __thread int tls;
|
||||
(void)tls;
|
||||
}
|
||||
" HAVE_THREAD_LOCAL)
|
||||
if(HAVE_THREAD_LOCAL)
|
||||
add_definitions(-DROCKSDB_SUPPORT_THREAD_LOCAL)
|
||||
endif()
|
||||
|
||||
option(WITH_IOSTATS_CONTEXT "Enable IO stats context" ON)
|
||||
if (NOT WITH_IOSTATS_CONTEXT)
|
||||
add_definitions(-DNIOSTATS_CONTEXT)
|
||||
@@ -373,7 +409,7 @@ endif()
|
||||
|
||||
option(WITH_TSAN "build with TSAN" OFF)
|
||||
if(WITH_TSAN)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread -Wl,-pie")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread -pie")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -fPIC")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=thread -fPIC")
|
||||
if(WITH_JEMALLOC)
|
||||
@@ -424,32 +460,30 @@ if (ASSERT_STATUS_CHECKED)
|
||||
add_definitions(-DROCKSDB_ASSERT_STATUS_CHECKED)
|
||||
endif()
|
||||
|
||||
|
||||
# RTTI is by default AUTO which enables it in debug and disables it in release.
|
||||
set(USE_RTTI AUTO CACHE STRING "Enable RTTI in builds")
|
||||
set_property(CACHE USE_RTTI PROPERTY STRINGS AUTO ON OFF)
|
||||
if(USE_RTTI STREQUAL "AUTO")
|
||||
if(DEFINED USE_RTTI)
|
||||
if(USE_RTTI)
|
||||
message(STATUS "Enabling RTTI")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DROCKSDB_USE_RTTI")
|
||||
else()
|
||||
if(MSVC)
|
||||
message(STATUS "Disabling RTTI in Release builds. Always on in Debug.")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
|
||||
else()
|
||||
message(STATUS "Disabling RTTI in Release builds")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-rtti")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "Enabling RTTI in Debug builds only (default)")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
|
||||
endif()
|
||||
elseif(USE_RTTI)
|
||||
message(STATUS "Enabling RTTI in all builds")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -DROCKSDB_USE_RTTI")
|
||||
else()
|
||||
if(MSVC)
|
||||
message(STATUS "Disabling RTTI in Release builds. Always on in Debug.")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DROCKSDB_USE_RTTI")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GR-")
|
||||
else()
|
||||
message(STATUS "Disabling RTTI in all builds")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-rtti")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-rtti")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Used to run CI build and tests so we can run faster
|
||||
@@ -578,68 +612,10 @@ if(HAVE_AUXV_GETAUXVAL)
|
||||
add_definitions(-DROCKSDB_AUXV_GETAUXVAL_PRESENT)
|
||||
endif()
|
||||
|
||||
check_cxx_symbol_exists(F_FULLFSYNC "fcntl.h" HAVE_FULLFSYNC)
|
||||
if(HAVE_FULLFSYNC)
|
||||
add_definitions(-DHAVE_FULLFSYNC)
|
||||
endif()
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
if(USE_COROUTINES)
|
||||
if(USE_FOLLY OR USE_FOLLY_LITE)
|
||||
message(FATAL_ERROR "Please specify exactly one of USE_COROUTINES,"
|
||||
" USE_FOLLY, and USE_FOLLY_LITE")
|
||||
endif()
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcoroutines -Wno-maybe-uninitialized")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-redundant-move")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-memory-model")
|
||||
add_compile_definitions(USE_COROUTINES)
|
||||
set(USE_FOLLY 1)
|
||||
endif()
|
||||
|
||||
if(USE_FOLLY)
|
||||
if(USE_FOLLY_LITE)
|
||||
message(FATAL_ERROR "Please specify one of USE_FOLLY or USE_FOLLY_LITE")
|
||||
endif()
|
||||
if(ROCKSDB_BUILD_SHARED)
|
||||
message(FATAL_ERROR "Cannot build RocksDB shared library with folly")
|
||||
endif()
|
||||
set(ROCKSDB_BUILD_SHARED OFF)
|
||||
set(GFLAGS_SHARED FALSE)
|
||||
find_package(folly)
|
||||
# If cmake could not find the folly-config.cmake file, fall back
|
||||
# to looking in third-party/folly for folly and its dependencies
|
||||
if(NOT FOLLY_LIBRARIES)
|
||||
exec_program(python3 ${PROJECT_SOURCE_DIR}/third-party/folly ARGS
|
||||
build/fbcode_builder/getdeps.py show-inst-dir OUTPUT_VARIABLE
|
||||
FOLLY_INST_PATH)
|
||||
exec_program(ls ARGS -d ${FOLLY_INST_PATH}/../boost* OUTPUT_VARIABLE
|
||||
BOOST_INST_PATH)
|
||||
exec_program(ls ARGS -d ${FOLLY_INST_PATH}/../fmt* OUTPUT_VARIABLE
|
||||
FMT_INST_PATH)
|
||||
exec_program(ls ARGS -d ${FOLLY_INST_PATH}/../gflags* OUTPUT_VARIABLE
|
||||
GFLAGS_INST_PATH)
|
||||
set(Boost_DIR ${BOOST_INST_PATH}/lib/cmake/Boost-1.78.0)
|
||||
if(EXISTS ${FMT_INST_PATH}/lib64)
|
||||
set(fmt_DIR ${FMT_INST_PATH}/lib64/cmake/fmt)
|
||||
else()
|
||||
set(fmt_DIR ${FMT_INST_PATH}/lib/cmake/fmt)
|
||||
endif()
|
||||
set(gflags_DIR ${GFLAGS_INST_PATH}/lib/cmake/gflags)
|
||||
|
||||
exec_program(sed ARGS -i 's/gflags_shared//g'
|
||||
${FOLLY_INST_PATH}/lib/cmake/folly/folly-targets.cmake)
|
||||
|
||||
include(${FOLLY_INST_PATH}/lib/cmake/folly/folly-config.cmake)
|
||||
endif()
|
||||
|
||||
add_compile_definitions(USE_FOLLY FOLLY_NO_CONFIG HAVE_CXX11_ATOMIC)
|
||||
list(APPEND THIRDPARTY_LIBS Folly::folly)
|
||||
set(FOLLY_LIBS Folly::folly)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--copy-dt-needed-entries")
|
||||
if(WITH_FOLLY_DISTRIBUTED_MUTEX)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
|
||||
endif()
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
@@ -648,16 +624,11 @@ find_package(Threads REQUIRED)
|
||||
set(SOURCES
|
||||
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
|
||||
@@ -669,8 +640,6 @@ set(SOURCES
|
||||
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
|
||||
@@ -681,11 +650,7 @@ set(SOURCES
|
||||
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/compaction_outputs.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
|
||||
@@ -720,11 +685,10 @@ set(SOURCES
|
||||
db/merge_helper.cc
|
||||
db/merge_operator.cc
|
||||
db/output_validator.cc
|
||||
db/periodic_task_scheduler.cc
|
||||
db/periodic_work_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
|
||||
@@ -736,8 +700,6 @@ set(SOURCES
|
||||
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
|
||||
@@ -746,6 +708,7 @@ set(SOURCES
|
||||
env/env.cc
|
||||
env/env_chroot.cc
|
||||
env/env_encryption.cc
|
||||
env/env_hdfs.cc
|
||||
env/file_system.cc
|
||||
env/file_system_tracer.cc
|
||||
env/fs_remap.cc
|
||||
@@ -769,7 +732,6 @@ set(SOURCES
|
||||
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
|
||||
@@ -800,6 +762,7 @@ set(SOURCES
|
||||
table/adaptive/adaptive_table_factory.cc
|
||||
table/block_based/binary_search_index_reader.cc
|
||||
table/block_based/block.cc
|
||||
table/block_based/block_based_filter_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
|
||||
@@ -862,12 +825,9 @@ set(SOURCES
|
||||
trace_replay/trace_record_result.cc
|
||||
trace_replay/trace_record.cc
|
||||
trace_replay/trace_replay.cc
|
||||
util/async_file_reader.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
|
||||
@@ -877,16 +837,15 @@ set(SOURCES
|
||||
util/random.cc
|
||||
util/rate_limiter.cc
|
||||
util/ribbon_config.cc
|
||||
util/regex.cc
|
||||
util/slice.cc
|
||||
util/file_checksum_helper.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/backupable/backupable_db.cc
|
||||
utilities/blob_db/blob_compaction_filter.cc
|
||||
utilities/blob_db/blob_db.cc
|
||||
utilities/blob_db/blob_db_impl.cc
|
||||
@@ -901,13 +860,11 @@ set(SOURCES
|
||||
utilities/checkpoint/checkpoint_impl.cc
|
||||
utilities/compaction_filters.cc
|
||||
utilities/compaction_filters/remove_emptyvalue_compactionfilter.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
|
||||
@@ -967,31 +924,6 @@ list(APPEND SOURCES
|
||||
utilities/transactions/lock/range/range_tree/lib/util/dbt.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/util/memarena.cc)
|
||||
|
||||
message(STATUS "ROCKSDB_PLUGINS: ${ROCKSDB_PLUGINS}")
|
||||
if ( ROCKSDB_PLUGINS )
|
||||
string(REPLACE " " ";" PLUGINS ${ROCKSDB_PLUGINS})
|
||||
foreach (plugin ${PLUGINS})
|
||||
add_subdirectory("plugin/${plugin}")
|
||||
foreach (src ${${plugin}_SOURCES})
|
||||
list(APPEND SOURCES plugin/${plugin}/${src})
|
||||
set_source_files_properties(
|
||||
plugin/${plugin}/${src}
|
||||
PROPERTIES COMPILE_FLAGS "${${plugin}_COMPILE_FLAGS}")
|
||||
endforeach()
|
||||
foreach (path ${${plugin}_INCLUDE_PATHS})
|
||||
include_directories(${path})
|
||||
endforeach()
|
||||
foreach (lib ${${plugin}_LIBS})
|
||||
list(APPEND THIRDPARTY_LIBS ${lib})
|
||||
endforeach()
|
||||
foreach (link_path ${${plugin}_LINK_PATHS})
|
||||
link_directories(AFTER ${link_path})
|
||||
endforeach()
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${${plugin}_CMAKE_SHARED_LINKER_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${${plugin}_CMAKE_EXE_LINKER_FLAGS}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if(HAVE_SSE42 AND NOT MSVC)
|
||||
set_source_files_properties(
|
||||
util/crc32c.cc
|
||||
@@ -1035,24 +967,26 @@ else()
|
||||
env/io_posix.cc)
|
||||
endif()
|
||||
|
||||
if(USE_FOLLY_LITE)
|
||||
if(WITH_FOLLY_DISTRIBUTED_MUTEX)
|
||||
list(APPEND SOURCES
|
||||
third-party/folly/folly/container/detail/F14Table.cpp
|
||||
third-party/folly/folly/detail/Futex.cpp
|
||||
third-party/folly/folly/lang/SafeAssert.cpp
|
||||
third-party/folly/folly/lang/ToAscii.cpp
|
||||
third-party/folly/folly/ScopeGuard.cpp
|
||||
third-party/folly/folly/synchronization/AtomicNotification.cpp
|
||||
third-party/folly/folly/synchronization/DistributedMutex.cpp
|
||||
third-party/folly/folly/synchronization/ParkingLot.cpp)
|
||||
include_directories(${PROJECT_SOURCE_DIR}/third-party/folly)
|
||||
add_definitions(-DUSE_FOLLY -DFOLLY_NO_CONFIG)
|
||||
list(APPEND THIRDPARTY_LIBS glog)
|
||||
third-party/folly/folly/synchronization/ParkingLot.cpp
|
||||
third-party/folly/folly/synchronization/WaitOptions.cpp)
|
||||
endif()
|
||||
|
||||
set(ROCKSDB_STATIC_LIB rocksdb${ARTIFACT_SUFFIX})
|
||||
set(ROCKSDB_SHARED_LIB rocksdb-shared${ARTIFACT_SUFFIX})
|
||||
|
||||
option(ROCKSDB_BUILD_SHARED "Build shared versions of the RocksDB libraries" ON)
|
||||
|
||||
option(WITH_LIBRADOS "Build with librados" OFF)
|
||||
if(WITH_LIBRADOS)
|
||||
list(APPEND SOURCES
|
||||
utilities/env_librados.cc)
|
||||
list(APPEND THIRDPARTY_LIBS rados)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(SYSTEM_LIBS ${SYSTEM_LIBS} shlwapi.lib rpcrt4.lib)
|
||||
@@ -1060,59 +994,6 @@ else()
|
||||
set(SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
set(ROCKSDB_PLUGIN_EXTERNS "")
|
||||
set(ROCKSDB_PLUGIN_BUILTINS "")
|
||||
message(STATUS "ROCKSDB PLUGINS TO BUILD ${ROCKSDB_PLUGINS}")
|
||||
foreach(PLUGIN IN LISTS PLUGINS)
|
||||
set(PLUGIN_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/plugin/${PLUGIN}/")
|
||||
message("including rocksb plugin ${PLUGIN_ROOT}")
|
||||
set(PLUGINMKFILE "${PLUGIN_ROOT}${PLUGIN}.mk")
|
||||
if (NOT EXISTS ${PLUGINMKFILE})
|
||||
message(FATAL_ERROR "Missing plugin makefile: ${PLUGINMKFILE}")
|
||||
endif()
|
||||
file(READ ${PLUGINMKFILE} PLUGINMK)
|
||||
string(REGEX MATCH "SOURCES = ([^\n]*)" FOO ${PLUGINMK})
|
||||
set(MK_SOURCES ${CMAKE_MATCH_1})
|
||||
separate_arguments(MK_SOURCES)
|
||||
foreach(MK_FILE IN LISTS MK_SOURCES)
|
||||
list(APPEND SOURCES "${PLUGIN_ROOT}${MK_FILE}")
|
||||
endforeach()
|
||||
string(REGEX MATCH "_FUNC = ([^\n]*)" FOO ${PLUGINMK})
|
||||
if (NOT ${CMAKE_MATCH_1} STREQUAL "")
|
||||
string(APPEND ROCKSDB_PLUGIN_BUILTINS "{\"${PLUGIN}\", " ${CMAKE_MATCH_1} "},")
|
||||
string(APPEND ROCKSDB_PLUGIN_EXTERNS "int " ${CMAKE_MATCH_1} "(ROCKSDB_NAMESPACE::ObjectLibrary&, const std::string&); ")
|
||||
endif()
|
||||
string(REGEX MATCH "_LIBS = ([^\n]*)" FOO ${PLUGINMK})
|
||||
if (NOT ${CMAKE_MATCH_1} STREQUAL "")
|
||||
list(APPEND THIRDPARTY_LIBS "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
message("THIRDPARTY_LIBS=${THIRDPARTY_LIBS}")
|
||||
#TODO: We need to set any compile/link-time flags and add any link libraries
|
||||
endforeach()
|
||||
|
||||
string(TIMESTAMP TS "%Y-%m-%d %H:%M:%S" UTC)
|
||||
set(BUILD_DATE "${TS}" CACHE STRING "the time we first built rocksdb")
|
||||
|
||||
find_package(Git)
|
||||
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git")
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_SHA COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD )
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE GIT_MOD COMMAND "${GIT_EXECUTABLE}" diff-index HEAD --quiet)
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_DATE COMMAND "${GIT_EXECUTABLE}" log -1 --date=format:"%Y-%m-%d %T" --format="%ad")
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG RESULT_VARIABLE rv COMMAND "${GIT_EXECUTABLE}" symbolic-ref -q --short HEAD OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (rv AND NOT rv EQUAL 0)
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG COMMAND "${GIT_EXECUTABLE}" describe --tags --exact-match OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
endif()
|
||||
else()
|
||||
set(GIT_SHA 0)
|
||||
set(GIT_MOD 1)
|
||||
endif()
|
||||
string(REGEX REPLACE "[^0-9a-fA-F]+" "" GIT_SHA "${GIT_SHA}")
|
||||
string(REGEX REPLACE "[^0-9: /-]+" "" GIT_DATE "${GIT_DATE}")
|
||||
|
||||
set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
|
||||
configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)
|
||||
|
||||
add_library(${ROCKSDB_STATIC_LIB} STATIC ${SOURCES} ${BUILD_VERSION_CC})
|
||||
target_link_libraries(${ROCKSDB_STATIC_LIB} PRIVATE
|
||||
${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
|
||||
@@ -1192,20 +1073,8 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
configure_file(
|
||||
${PROJECT_NAME}.pc.in
|
||||
${PROJECT_NAME}.pc
|
||||
@ONLY
|
||||
)
|
||||
|
||||
install(DIRECTORY include/rocksdb COMPONENT devel DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
foreach (plugin ${PLUGINS})
|
||||
foreach (header ${${plugin}_HEADERS})
|
||||
install(FILES plugin/${plugin}/${header} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rocksdb/plugin/${plugin})
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
install(DIRECTORY "${PROJECT_SOURCE_DIR}/cmake/modules" COMPONENT devel DESTINATION ${package_config_destination})
|
||||
|
||||
install(
|
||||
@@ -1242,13 +1111,6 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
COMPONENT devel
|
||||
DESTINATION ${package_config_destination}
|
||||
)
|
||||
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc
|
||||
COMPONENT devel
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
|
||||
)
|
||||
endif()
|
||||
|
||||
option(WITH_ALL_TESTS "Build all test, rather than a small subset" ON)
|
||||
@@ -1270,7 +1132,6 @@ if(WITH_TESTS)
|
||||
list(APPEND TESTS
|
||||
cache/cache_reservation_manager_test.cc
|
||||
cache/cache_test.cc
|
||||
cache/compressed_secondary_cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
db/blob/blob_counting_iterator_test.cc
|
||||
db/blob/blob_file_addition_test.cc
|
||||
@@ -1279,7 +1140,6 @@ if(WITH_TESTS)
|
||||
db/blob/blob_file_garbage_test.cc
|
||||
db/blob/blob_file_reader_test.cc
|
||||
db/blob/blob_garbage_meter_test.cc
|
||||
db/blob/blob_source_test.cc
|
||||
db/blob/db_blob_basic_test.cc
|
||||
db/blob/db_blob_compaction_test.cc
|
||||
db/blob/db_blob_corruption_test.cc
|
||||
@@ -1292,18 +1152,15 @@ if(WITH_TESTS)
|
||||
db/compaction/compaction_iterator_test.cc
|
||||
db/compaction/compaction_picker_test.cc
|
||||
db/compaction/compaction_service_test.cc
|
||||
db/compaction/tiered_compaction_test.cc
|
||||
db/comparator_db_test.cc
|
||||
db/corruption_test.cc
|
||||
db/cuckoo_table_db_test.cc
|
||||
db/db_readonly_with_timestamp_test.cc
|
||||
db/db_with_timestamp_basic_test.cc
|
||||
db/db_block_cache_test.cc
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_compaction_filter_test.cc
|
||||
db/db_compaction_test.cc
|
||||
db/db_dynamic_level_test.cc
|
||||
db/db_encryption_test.cc
|
||||
db/db_flush_test.cc
|
||||
db/db_inplace_update_test.cc
|
||||
db/db_io_failure_test.cc
|
||||
@@ -1318,7 +1175,6 @@ if(WITH_TESTS)
|
||||
db/db_options_test.cc
|
||||
db/db_properties_test.cc
|
||||
db/db_range_del_test.cc
|
||||
db/db_rate_limiter_test.cc
|
||||
db/db_secondary_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/db_statistics_test.cc
|
||||
@@ -1330,7 +1186,6 @@ if(WITH_TESTS)
|
||||
db/db_universal_compaction_test.cc
|
||||
db/db_wal_test.cc
|
||||
db/db_with_timestamp_compaction_test.cc
|
||||
db/db_write_buffer_manager_test.cc
|
||||
db/db_write_test.cc
|
||||
db/dbformat_test.cc
|
||||
db/deletefile_test.cc
|
||||
@@ -1342,7 +1197,6 @@ if(WITH_TESTS)
|
||||
db/file_indexer_test.cc
|
||||
db/filename_test.cc
|
||||
db/flush_job_test.cc
|
||||
db/import_column_family_test.cc
|
||||
db/listener_test.cc
|
||||
db/log_test.cc
|
||||
db/manual_compaction_test.cc
|
||||
@@ -1351,9 +1205,8 @@ if(WITH_TESTS)
|
||||
db/merge_test.cc
|
||||
db/options_file_test.cc
|
||||
db/perf_context_test.cc
|
||||
db/periodic_task_scheduler_test.cc
|
||||
db/periodic_work_scheduler_test.cc
|
||||
db/plain_table_db_test.cc
|
||||
db/seqno_time_test.cc
|
||||
db/prefix_test.cc
|
||||
db/range_del_aggregator_test.cc
|
||||
db/range_tombstone_fragmenter_test.cc
|
||||
@@ -1364,8 +1217,6 @@ if(WITH_TESTS)
|
||||
db/version_set_test.cc
|
||||
db/wal_manager_test.cc
|
||||
db/wal_edit_test.cc
|
||||
db/wide/db_wide_basic_test.cc
|
||||
db/wide/wide_column_serialization_test.cc
|
||||
db/write_batch_test.cc
|
||||
db/write_callback_test.cc
|
||||
db/write_controller_test.cc
|
||||
@@ -1379,7 +1230,7 @@ if(WITH_TESTS)
|
||||
logging/env_logger_test.cc
|
||||
logging/event_logger_test.cc
|
||||
memory/arena_test.cc
|
||||
memory/memory_allocator_test.cc
|
||||
memory/memkind_kmem_allocator_test.cc
|
||||
memtable/inlineskiplist_test.cc
|
||||
memtable/skiplist_test.cc
|
||||
memtable/write_buffer_manager_test.cc
|
||||
@@ -1391,6 +1242,7 @@ if(WITH_TESTS)
|
||||
options/customizable_test.cc
|
||||
options/options_settable_test.cc
|
||||
options/options_test.cc
|
||||
table/block_based/block_based_filter_block_test.cc
|
||||
table/block_based/block_based_table_reader_test.cc
|
||||
table/block_based/block_test.cc
|
||||
table/block_based/data_block_hash_index_test.cc
|
||||
@@ -1433,15 +1285,13 @@ if(WITH_TESTS)
|
||||
util/thread_list_test.cc
|
||||
util/thread_local_test.cc
|
||||
util/work_queue_test.cc
|
||||
utilities/agg_merge/agg_merge_test.cc
|
||||
utilities/backup/backup_engine_test.cc
|
||||
utilities/backupable/backupable_db_test.cc
|
||||
utilities/blob_db/blob_db_test.cc
|
||||
utilities/cassandra/cassandra_functional_test.cc
|
||||
utilities/cassandra/cassandra_format_test.cc
|
||||
utilities/cassandra/cassandra_row_merge_test.cc
|
||||
utilities/cassandra/cassandra_serialize_test.cc
|
||||
utilities/checkpoint/checkpoint_test.cc
|
||||
utilities/env_timed_test.cc
|
||||
utilities/memory/memory_test.cc
|
||||
utilities/merge_operators/string_append/stringappend_test.cc
|
||||
utilities/object_registry_test.cc
|
||||
@@ -1455,30 +1305,32 @@ if(WITH_TESTS)
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
utilities/transactions/transaction_test.cc
|
||||
utilities/transactions/lock/point/point_lock_manager_test.cc
|
||||
utilities/transactions/write_committed_transaction_ts_test.cc
|
||||
utilities/transactions/write_prepared_transaction_test.cc
|
||||
utilities/transactions/write_unprepared_transaction_test.cc
|
||||
utilities/transactions/lock/range/range_locking_test.cc
|
||||
utilities/transactions/timestamped_snapshot_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/util_merge_operators_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
)
|
||||
endif()
|
||||
if(WITH_LIBRADOS)
|
||||
list(APPEND TESTS utilities/env_librados_test.cc)
|
||||
endif()
|
||||
|
||||
if(WITH_FOLLY_DISTRIBUTED_MUTEX)
|
||||
list(APPEND TESTS third-party/folly/folly/synchronization/test/DistributedMutexTest.cpp)
|
||||
endif()
|
||||
|
||||
set(TESTUTIL_SOURCE
|
||||
db/db_test_util.cc
|
||||
db/db_with_timestamp_test_util.cc
|
||||
monitoring/thread_status_updater_debug.cc
|
||||
table/mock_table.cc
|
||||
utilities/agg_merge/test_agg_merge.cc
|
||||
utilities/cassandra/test_utils.cc
|
||||
)
|
||||
enable_testing()
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND})
|
||||
set(TESTUTILLIB testutillib${ARTIFACT_SUFFIX})
|
||||
add_library(${TESTUTILLIB} STATIC ${TESTUTIL_SOURCE})
|
||||
target_link_libraries(${TESTUTILLIB} ${ROCKSDB_LIB} ${FOLLY_LIBS})
|
||||
target_link_libraries(${TESTUTILLIB} ${ROCKSDB_LIB})
|
||||
if(MSVC)
|
||||
set_target_properties(${TESTUTILLIB} PROPERTIES COMPILE_FLAGS "/Fd${CMAKE_CFG_INTDIR}/testutillib${ARTIFACT_SUFFIX}.pdb")
|
||||
endif()
|
||||
@@ -1502,6 +1354,10 @@ if(WITH_TESTS)
|
||||
gtest_discover_tests(${exename} DISCOVERY_TIMEOUT 120)
|
||||
add_dependencies(check ${exename}${ARTIFACT_SUFFIX})
|
||||
endif()
|
||||
if("${exename}" MATCHES "env_librados_test")
|
||||
# env_librados_test.cc uses librados directly
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} rados)
|
||||
endif()
|
||||
endforeach(sourcefile ${TESTS})
|
||||
|
||||
if(WIN32)
|
||||
@@ -1536,46 +1392,32 @@ if(WITH_BENCHMARK_TOOLS)
|
||||
cache/cache_bench.cc
|
||||
cache/cache_bench_tool.cc)
|
||||
target_link_libraries(cache_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(memtablerep_bench${ARTIFACT_SUFFIX}
|
||||
memtable/memtablerep_bench.cc)
|
||||
target_link_libraries(memtablerep_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(range_del_aggregator_bench${ARTIFACT_SUFFIX}
|
||||
db/range_del_aggregator_bench.cc)
|
||||
target_link_libraries(range_del_aggregator_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(table_reader_bench${ARTIFACT_SUFFIX}
|
||||
table/table_reader_bench.cc)
|
||||
target_link_libraries(table_reader_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} testharness ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
${ROCKSDB_LIB} testharness ${GFLAGS_LIB})
|
||||
|
||||
add_executable(filter_bench${ARTIFACT_SUFFIX}
|
||||
util/filter_bench.cc)
|
||||
target_link_libraries(filter_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(hash_table_bench${ARTIFACT_SUFFIX}
|
||||
utilities/persistent_cache/hash_table_bench.cc)
|
||||
target_link_libraries(hash_table_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
endif()
|
||||
|
||||
option(WITH_TRACE_TOOLS "build with trace tools" ON)
|
||||
if(WITH_TRACE_TOOLS)
|
||||
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_tool${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
|
||||
add_executable(trace_analyzer${ARTIFACT_SUFFIX}
|
||||
tools/trace_analyzer.cc)
|
||||
target_link_libraries(trace_analyzer${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB} ${FOLLY_LIBS})
|
||||
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
endif()
|
||||
|
||||
if(WITH_CORE_TOOLS OR WITH_TOOLS)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# RocksDB default options change log (NO LONGER MAINTAINED)
|
||||
# RocksDB default options change log
|
||||
## Unreleased
|
||||
* delayed_write_rate takes the rate given by rate_limiter if not specified.
|
||||
|
||||
|
||||
+4
-541
@@ -1,543 +1,7 @@
|
||||
# Rocksdb Change Log
|
||||
## 7.7.8 (11/27/2022)
|
||||
# Rocksdb Change Lo
|
||||
## 6.26.1 (2021-11-18)
|
||||
### Bug Fixes
|
||||
* 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`).
|
||||
* Tiered Storage: fixed excessive keys written to penultimate level in non-debug builds.
|
||||
* Fixed a regression in iterator where range tombstones after `iterate_upper_bound` is processed.
|
||||
|
||||
## 7.7.7 (11/15/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a regression in scan for async_io. During seek, valid buffers were getting cleared causing a regression.
|
||||
|
||||
## 7.7.6 (11/03/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.7.5 (10/28/2022)
|
||||
### Bug Fixes
|
||||
* Fixed an iterator performance regression for delete range users when scanning through a consecutive sequence of range tombstones (#10877).
|
||||
|
||||
## 7.7.4 (10/28/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a case of calling malloc_usable_size on result of operator new[].
|
||||
|
||||
## 7.7.3 (10/11/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a memory safety bug in experimental HyperClockCache (#10768)
|
||||
|
||||
## 7.7.2 (10/05/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in iterator refresh that was not freeing up SuperVersion, which could cause excessive resource pinniung (#10770).
|
||||
* Fixed a bug where RocksDB could be doing compaction endlessly when allow_ingest_behind is true and the bottommost level is not filled (#10767).
|
||||
|
||||
### 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).
|
||||
|
||||
## 7.7.1 (09/26/2022)
|
||||
### Bug Fixes
|
||||
* Fixed an optimistic transaction validation bug caused by DBImpl::GetLatestSequenceForKey() returning non-latest seq for merge (#10724).
|
||||
* Fixed a bug in iterator refresh which could segfault for DeleteRange users (#10739).
|
||||
|
||||
## 7.7.0 (09/18/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a hang when an operation such as `GetLiveFiles` or `CreateNewBackup` is asked to trigger and wait for memtable flush on a read-only DB. Such indirect requests for memtable flush are now ignored on a read-only DB.
|
||||
* Fixed bug where `FlushWAL(true /* sync */)` (used by `GetLiveFilesStorageInfo()`, which is used by checkpoint and backup) could cause parallel writes at the tail of a WAL file to never be synced.
|
||||
* Fix periodic_task unable to re-register the same task type, which may cause `SetOptions()` fail to update periodical_task time like: `stats_dump_period_sec`, `stats_persist_period_sec`.
|
||||
* Fixed a bug in the rocksdb.prefetched.bytes.discarded stat. It was counting the prefetch buffer size, rather than the actual number of bytes discarded from the buffer.
|
||||
* Fix bug where the directory containing CURRENT can left unsynced after CURRENT is updated to point to the latest MANIFEST, which leads to risk of unsync data loss of CURRENT.
|
||||
* Update rocksdb.multiget.io.batch.size stat in non-async MultiGet as well.
|
||||
* Fix a bug in key range overlap checking with concurrent compactions when user-defined timestamp is enabled. User-defined timestamps should be EXCLUDED when checking if two ranges overlap.
|
||||
* Fixed a bug where the blob cache prepopulating logic did not consider the secondary cache (see #10603).
|
||||
* Fixed the rocksdb.num.sst.read.per.level, rocksdb.num.index.and.filter.blocks.read.per.level and rocksdb.num.level.read.per.multiget stats in the MultiGet coroutines
|
||||
* Fix a bug in io_uring_prep_cancel in AbortIO API for posix which expects sqe->addr to match with read request submitted and wrong paramter was being passed.
|
||||
* Fixed a regression in iterator performance when the entire DB is a single memtable introduced in #10449. The fix is in #10705 and #10716.
|
||||
* Fix a bug in io_uring_prep_cancel in AbortIO API for posix which expects sqe->addr to match with read request submitted and wrong paramter was being passed.
|
||||
* Fixed a regression in iterator performance when the entire DB is a single memtable introduced in #10449. The fix is in #10705 and #10716.
|
||||
|
||||
### Public API changes
|
||||
* Add `rocksdb_column_family_handle_get_id`, `rocksdb_column_family_handle_get_name` to get name, id of column family in C API
|
||||
* Add a new stat rocksdb.async.prefetch.abort.micros to measure time spent waiting for async prefetch reads to abort
|
||||
|
||||
### Java API Changes
|
||||
* Add CompactionPriority.RoundRobin.
|
||||
* Revert to using the default metadata charge policy when creating an LRU cache via the Java API.
|
||||
|
||||
### Behavior Change
|
||||
* DBOptions::verify_sst_unique_id_in_manifest is now an on-by-default feature that verifies SST file identity whenever they are opened by a DB, rather than only at DB::Open time.
|
||||
* Right now, when the option migration tool (OptionChangeMigration()) migrates to FIFO compaction, it compacts all the data into one single SST file and move to L0. This might create a problem for some users: the giant file may be soon deleted to satisfy max_table_files_size, and might cayse the DB to be almost empty. We change the behavior so that the files are cut to be smaller, but these files might not follow the data insertion order. With the change, after the migration, migrated data might not be dropped by insertion order by FIFO compaction.
|
||||
* When a block is firstly found from `CompressedSecondaryCache`, we just insert a dummy block into the primary cache and don’t erase the block from `CompressedSecondaryCache`. A standalone handle is returned to the caller. Only if the block is found again from `CompressedSecondaryCache` before the dummy block is evicted, we erase the block from `CompressedSecondaryCache` and insert it into the primary cache.
|
||||
* When a block is firstly evicted from the primary cache to `CompressedSecondaryCache`, we just insert a dummy block in `CompressedSecondaryCache`. Only if it is evicted again before the dummy block is evicted from the cache, it is treated as a hot block and is inserted into `CompressedSecondaryCache`.
|
||||
* Improved the estimation of memory used by cached blobs by taking into account the size of the object owning the blob value and also the allocator overhead if `malloc_usable_size` is available (see #10583).
|
||||
* Blob values now have their own category in the cache occupancy statistics, as opposed to being lumped into the "Misc" bucket (see #10601).
|
||||
* Change the optimize_multiget_for_io experimental ReadOptions flag to default on.
|
||||
|
||||
### New Features
|
||||
* RocksDB does internal auto prefetching if it notices 2 sequential reads if readahead_size is not specified. New option `num_file_reads_for_auto_readahead` is added in BlockBasedTableOptions which indicates after how many sequential reads internal auto prefetching should be start (default is 2).
|
||||
* Added new perf context counters `block_cache_standalone_handle_count`, `block_cache_real_handle_count`,`compressed_sec_cache_insert_real_count`, `compressed_sec_cache_insert_dummy_count`, `compressed_sec_cache_uncompressed_bytes`, and `compressed_sec_cache_compressed_bytes`.
|
||||
* Memory for blobs which are to be inserted into the blob cache is now allocated using the cache's allocator (see #10628 and #10647).
|
||||
* HyperClockCache is an experimental, lock-free Cache alternative for block cache that offers much improved CPU efficiency under high parallel load or high contention, with some caveats. As much as 4.5x higher ops/sec vs. LRUCache has been seen in db_bench under high parallel load.
|
||||
* `CompressedSecondaryCacheOptions::enable_custom_split_merge` is added for enabling the custom split and merge feature, which split the compressed value into chunks so that they may better fit jemalloc bins.
|
||||
|
||||
### Performance Improvements
|
||||
* Iterator performance is improved for `DeleteRange()` users. Internally, iterator will skip to the end of a range tombstone when possible, instead of looping through each key and check individually if a key is range deleted.
|
||||
* Eliminated some allocations and copies in the blob read path. Also, `PinnableSlice` now only points to the blob value and pins the backing resource (cache entry or buffer) in all cases, instead of containing a copy of the blob value. See #10625 and #10647.
|
||||
* In case of scans with async_io enabled, few optimizations have been added to issue more asynchronous requests in parallel in order to avoid synchronous prefetching.
|
||||
* `DeleteRange()` users should see improvement in get/iterator performance from mutable memtable (see #10547).
|
||||
|
||||
## 7.6.0 (08/19/2022)
|
||||
### New Features
|
||||
* Added `prepopulate_blob_cache` to ColumnFamilyOptions. If enabled, prepopulate warm/hot blobs which are already in memory into blob cache at the time of flush. On a flush, the blob that is in memory (in memtables) get flushed to the device. If using Direct IO, additional IO is incurred to read this blob back into memory again, which is avoided by enabling this option. This further helps if the workload exhibits high temporal locality, where most of the reads go to recently written data. This also helps in case of the remote file system since it involves network traffic and higher latencies.
|
||||
* Support using secondary cache with the blob cache. When creating a blob cache, the user can set a secondary blob cache by configuring `secondary_cache` in LRUCacheOptions.
|
||||
* Charge memory usage of blob cache when the backing cache of the blob cache and the block cache are different. If an operation reserving memory for blob cache exceeds the avaible space left in the block cache at some point (i.e, causing a cache full under `LRUCacheOptions::strict_capacity_limit` = true), creation will fail with `Status::MemoryLimit()`. To opt in this feature, enable charging `CacheEntryRole::kBlobCache` in `BlockBasedTableOptions::cache_usage_options`.
|
||||
* Improve subcompaction range partition so that it is likely to be more even. More evenly distribution of subcompaction will improve compaction throughput for some workloads. All input files' index blocks to sample some anchor key points from which we pick positions to partition the input range. This would introduce some CPU overhead in compaction preparation phase, if subcompaction is enabled, but it should be a small fraction of the CPU usage of the whole compaction process. This also brings a behavier change: subcompaction number is much more likely to maxed out than before.
|
||||
* Add CompactionPri::kRoundRobin, a compaction picking mode that cycles through all the files with a compact cursor in a round-robin manner. This feature is available since 7.5.
|
||||
* Provide support for subcompactions for user_defined_timestamp.
|
||||
* Added an option `memtable_protection_bytes_per_key` that turns on memtable per key-value checksum protection. Each memtable entry will be suffixed by a checksum that is computed during writes, and verified in reads/compaction. Detected corruption will be logged and with corruption status returned to user.
|
||||
* Added a blob-specific cache priority level - bottom level. Blobs are typically lower-value targets for caching than data blocks, since 1) with BlobDB, data blocks containing blob references conceptually form an index structure which has to be consulted before we can read the blob value, and 2) cached blobs represent only a single key-value, while cached data blocks generally contain multiple KVs. The user can specify the new option `low_pri_pool_ratio` in `LRUCacheOptions` to configure the ratio of capacity reserved for low priority cache entries (and therefore the remaining ratio is the space reserved for the bottom level), or configuring the new argument `low_pri_pool_ratio` in `NewLRUCache()` to achieve the same effect.
|
||||
|
||||
### Public API changes
|
||||
* Removed Customizable support for RateLimiter and removed its CreateFromString() and Type() functions.
|
||||
* `CompactRangeOptions::exclusive_manual_compaction` is now false by default. This ensures RocksDB does not introduce artificial parallelism limitations by default.
|
||||
* Tiered Storage: change `bottommost_temperture` to `last_level_temperture`. The old option name is kept only for migration, please use the new option. The behavior is changed to apply temperature for the `last_level` SST files only.
|
||||
* Added a new experimental ReadOption flag called optimize_multiget_for_io, which when set attempts to reduce MultiGet latency by spawning coroutines for keys in multiple levels.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug starting in 7.4.0 in which some fsync operations might be skipped in a DB after any DropColumnFamily on that DB, until it is re-opened. This can lead to data loss on power loss. (For custom FileSystem implementations, this could lead to `FSDirectory::Fsync` or `FSDirectory::Close` after the first `FSDirectory::Close`; Also, valgrind could report call to `close()` with `fd=-1`.)
|
||||
* Fix a bug where `GenericRateLimiter` could revert the bandwidth set dynamically using `SetBytesPerSecond()` when a user configures a structure enclosing it, e.g., using `GetOptionsFromString()` to configure an `Options` that references an existing `RateLimiter` object.
|
||||
* Fix race conditions in `GenericRateLimiter`.
|
||||
* Fix a bug in `FIFOCompactionPicker::PickTTLCompaction` where total_size calculating might cause underflow
|
||||
* Fix data race bug in hash linked list memtable. With this bug, read request might temporarily miss an old record in the memtable in a race condition to the hash bucket.
|
||||
* Fix a bug that `best_efforts_recovery` may fail to open the db with mmap read.
|
||||
* Fixed a bug where blobs read during compaction would pollute the cache.
|
||||
* Fixed a data race in LRUCache when used with a secondary_cache.
|
||||
* Fixed a bug where blobs read by iterators would be inserted into the cache even with the `fill_cache` read option set to false.
|
||||
* Fixed the segfault caused by `AllocateData()` in `CompressedSecondaryCache::SplitValueIntoChunks()` and `MergeChunksIntoValueTest`.
|
||||
* Fixed a bug in BlobDB where a mix of inlined and blob values could result in an incorrect value being passed to the compaction filter (see #10391).
|
||||
* Fixed a memory leak bug in stress tests caused by `FaultInjectionSecondaryCache`.
|
||||
|
||||
### Behavior Change
|
||||
* Added checksum handshake during the copying of decompressed WAL fragment. This together with #9875, #10037, #10212, #10114 and #10319 provides end-to-end integrity protection for write batch during recovery.
|
||||
* To minimize the internal fragmentation caused by the variable size of the compressed blocks in `CompressedSecondaryCache`, the original block is split according to the jemalloc bin size in `Insert()` and then merged back in `Lookup()`.
|
||||
* PosixLogger is removed and by default EnvLogger will be used for info logging. The behavior of the two loggers should be very similar when using the default Posix Env.
|
||||
* Remove [min|max]_timestamp from VersionEdit for now since they are not tracked in MANIFEST anyway but consume two empty std::string (up to 64 bytes) for each file. Should they be added back in the future, we should store them more compactly.
|
||||
* Improve universal tiered storage compaction picker to avoid extra major compaction triggered by size amplification. If `preclude_last_level_data_seconds` is enabled, the size amplification is calculated within non last_level data only which skip the last level and use the penultimate level as the size base.
|
||||
* If an error is hit when writing to a file (append, sync, etc), RocksDB is more strict with not issuing more operations to it, except closing the file, with exceptions of some WAL file operations in error recovery path.
|
||||
* A `WriteBufferManager` constructed with `allow_stall == false` will no longer trigger write stall implicitly by thrashing until memtable count limit is reached. Instead, a column family can continue accumulating writes while that CF is flushing, which means memory may increase. Users who prefer stalling writes must now explicitly set `allow_stall == true`.
|
||||
* Add `CompressedSecondaryCache` into the stress tests.
|
||||
* Block cache keys have changed, which will cause any persistent caches to miss between versions.
|
||||
|
||||
### Performance Improvements
|
||||
* Instead of constructing `FragmentedRangeTombstoneList` during every read operation, it is now constructed once and stored in immutable memtables. This improves speed of querying range tombstones from immutable memtables.
|
||||
* When using iterators with the integrated BlobDB implementation, blob cache handles are now released immediately when the iterator's position changes.
|
||||
* MultiGet can now do more IO in parallel by reading data blocks from SST files in multiple levels, if the optimize_multiget_for_io ReadOption flag is set.
|
||||
|
||||
## 7.5.0 (07/15/2022)
|
||||
### New Features
|
||||
* Mempurge option flag `experimental_mempurge_threshold` is now a ColumnFamilyOptions and can now be dynamically configured using `SetOptions()`.
|
||||
* Support backward iteration when `ReadOptions::iter_start_ts` is set.
|
||||
* Provide support for ReadOptions.async_io with direct_io to improve Seek latency by using async IO to parallelize child iterator seek and doing asynchronous prefetching on sequential scans.
|
||||
* Added support for blob caching in order to cache frequently used blobs for BlobDB.
|
||||
* User can configure the new ColumnFamilyOptions `blob_cache` to enable/disable blob caching.
|
||||
* Either sharing the backend cache with the block cache or using a completely separate cache is supported.
|
||||
* A new abstraction interface called `BlobSource` for blob read logic gives all users access to blobs, whether they are in the blob cache, secondary cache, or (remote) storage. Blobs can be potentially read both while handling user reads (`Get`, `MultiGet`, or iterator) and during compaction (while dealing with compaction filters, Merges, or garbage collection) but eventually all blob reads go through `Version::GetBlob` or, for MultiGet, `Version::MultiGetBlob` (and then get dispatched to the interface -- `BlobSource`).
|
||||
* Add experimental tiered compaction feature `AdvancedColumnFamilyOptions::preclude_last_level_data_seconds`, which makes sure the new data inserted within preclude_last_level_data_seconds won't be placed on cold tier (the feature is not complete).
|
||||
|
||||
### Public API changes
|
||||
* Add metadata related structs and functions in C API, including
|
||||
* `rocksdb_get_column_family_metadata()` and `rocksdb_get_column_family_metadata_cf()` to obtain `rocksdb_column_family_metadata_t`.
|
||||
* `rocksdb_column_family_metadata_t` and its get functions & destroy function.
|
||||
* `rocksdb_level_metadata_t` and its and its get functions & destroy function.
|
||||
* `rocksdb_file_metadata_t` and its and get functions & destroy functions.
|
||||
* Add suggest_compact_range() and suggest_compact_range_cf() to C API.
|
||||
* When using block cache strict capacity limit (`LRUCache` with `strict_capacity_limit=true`), DB operations now fail with Status code `kAborted` subcode `kMemoryLimit` (`IsMemoryLimit()`) instead of `kIncomplete` (`IsIncomplete()`) when the capacity limit is reached, because Incomplete can mean other specific things for some operations. In more detail, `Cache::Insert()` now returns the updated Status code and this usually propagates through RocksDB to the user on failure.
|
||||
* NewClockCache calls temporarily return an LRUCache (with similar characteristics as the desired ClockCache). This is because ClockCache is being replaced by a new version (the old one had unknown bugs) but this is still under development.
|
||||
* Add two functions `int ReserveThreads(int threads_to_be_reserved)` and `int ReleaseThreads(threads_to_be_released)` into `Env` class. In the default implementation, both return 0. Newly added `xxxEnv` class that inherits `Env` should implement these two functions for thread reservation/releasing features.
|
||||
* Add `rocksdb_options_get_prepopulate_blob_cache` and `rocksdb_options_set_prepopulate_blob_cache` to C API.
|
||||
* Add `prepopulateBlobCache` and `setPrepopulateBlobCache` to Java API.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug in which backup/checkpoint can include a WAL deleted by RocksDB.
|
||||
* Fix a bug where concurrent compactions might cause unnecessary further write stalling. In some cases, this might cause write rate to drop to minimum.
|
||||
* Fix a bug in Logger where if dbname and db_log_dir are on different filesystems, dbname creation would fail wrt to db_log_dir path returning an error and fails to open the DB.
|
||||
* Fix a CPU and memory efficiency issue introduce by https://github.com/facebook/rocksdb/pull/8336 which made InternalKeyComparator configurable as an unintended side effect.
|
||||
|
||||
## Behavior Change
|
||||
* In leveled compaction with dynamic levelling, level multiplier is not anymore adjusted due to oversized L0. Instead, compaction score is adjusted by increasing size level target by adding incoming bytes from upper levels. This would deprioritize compactions from upper levels if more data from L0 is coming. This is to fix some unnecessary full stalling due to drastic change of level targets, while not wasting write bandwidth for compaction while writes are overloaded.
|
||||
* For track_and_verify_wals_in_manifest, revert to the original behavior before #10087: syncing of live WAL file is not tracked, and we track only the synced sizes of **closed** WALs. (PR #10330).
|
||||
* WAL compression now computes/verifies checksum during compression/decompression.
|
||||
|
||||
### Performance Improvements
|
||||
* Rather than doing total sort against all files in a level, SortFileByOverlappingRatio() to only find the top 50 files based on score. This can improve write throughput for the use cases where data is loaded in increasing key order and there are a lot of files in one LSM-tree, where applying compaction results is the bottleneck.
|
||||
* In leveled compaction, L0->L1 trivial move will allow more than one file to be moved in one compaction. This would allow L0 files to be moved down faster when data is loaded in sequential order, making slowdown or stop condition harder to hit. Also seek L0->L1 trivial move when only some files qualify.
|
||||
* In leveled compaction, try to trivial move more than one files if possible, up to 4 files or max_compaction_bytes. This is to allow higher write throughput for some use cases where data is loaded in sequential order, where appying compaction results is the bottleneck.
|
||||
|
||||
## 7.4.0 (06/19/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in calculating key-value integrity protection for users of in-place memtable updates. In particular, the affected users would be those who configure `protection_bytes_per_key > 0` on `WriteBatch` or `WriteOptions`, and configure `inplace_callback != nullptr`.
|
||||
* Fixed a bug where a snapshot taken during SST file ingestion would be unstable.
|
||||
* Fixed a bug for non-TransactionDB with avoid_flush_during_recovery = true and TransactionDB where in case of crash, min_log_number_to_keep may not change on recovery and persisting a new MANIFEST with advanced log_numbers for some column families, results in "column family inconsistency" error on second recovery. As a solution, RocksDB will persist the new MANIFEST after successfully syncing the new WAL. If a future recovery starts from the new MANIFEST, then it means the new WAL is successfully synced. Due to the sentinel empty write batch at the beginning, kPointInTimeRecovery of WAL is guaranteed to go after this point. If future recovery starts from the old MANIFEST, it means the writing the new MANIFEST failed. We won't have the "SST ahead of WAL" error.
|
||||
* Fixed a bug where RocksDB DB::Open() may creates and writes to two new MANIFEST files even before recovery succeeds. Now writes to MANIFEST are persisted only after recovery is successful.
|
||||
* Fix a race condition in WAL size tracking which is caused by an unsafe iterator access after container is changed.
|
||||
* Fix unprotected concurrent accesses to `WritableFileWriter::filesize_` by `DB::SyncWAL()` and `DB::Put()` in two write queue mode.
|
||||
* Fix a bug in WAL tracking. Before this PR (#10087), calling `SyncWAL()` on the only WAL file of the db will not log the event in MANIFEST, thus allowing a subsequent `DB::Open` even if the WAL file is missing or corrupted.
|
||||
* Fix a bug that could return wrong results with `index_type=kHashSearch` and using `SetOptions` to change the `prefix_extractor`.
|
||||
* Fixed a bug in WAL tracking with wal_compression. WAL compression writes a kSetCompressionType record which is not associated with any sequence number. As result, WalManager::GetSortedWalsOfType() will skip these WALs and not return them to caller, e.g. Checkpoint, Backup, causing the operations to fail.
|
||||
* Avoid a crash if the IDENTITY file is accidentally truncated to empty. A new DB ID will be written and generated on Open.
|
||||
* Fixed a possible corruption for users of `manual_wal_flush` and/or `FlushWAL(true /* sync */)`, together with `track_and_verify_wals_in_manifest == true`. For those users, losing unsynced data (e.g., due to power loss) could make future DB opens fail with a `Status::Corruption` complaining about missing WAL data.
|
||||
* Fixed a bug in `WriteBatchInternal::Append()` where WAL termination point in write batch was not considered and the function appends an incorrect number of checksums.
|
||||
* Fixed a crash bug introduced in 7.3.0 affecting users of MultiGet with `kDataBlockBinaryAndHash`.
|
||||
|
||||
### Public API changes
|
||||
* Add new API GetUnixTime in Snapshot class which returns the unix time at which Snapshot is taken.
|
||||
* Add transaction `get_pinned` and `multi_get` to C API.
|
||||
* Add two-phase commit support to C API.
|
||||
* Add `rocksdb_transaction_get_writebatch_wi` and `rocksdb_transaction_rebuild_from_writebatch` to C API.
|
||||
* Add `rocksdb_options_get_blob_file_starting_level` and `rocksdb_options_set_blob_file_starting_level` to C API.
|
||||
* Add `blobFileStartingLevel` and `setBlobFileStartingLevel` to Java API.
|
||||
* Add SingleDelete for DB in C API
|
||||
* Add User Defined Timestamp in C API.
|
||||
* `rocksdb_comparator_with_ts_create` to create timestamp aware comparator
|
||||
* Put, Get, Delete, SingleDelete, MultiGet APIs has corresponding timestamp aware APIs with suffix `with_ts`
|
||||
* And Add C API's for Transaction, SstFileWriter, Compaction as mentioned [here](https://github.com/facebook/rocksdb/wiki/User-defined-Timestamp-(Experimental))
|
||||
* The contract for implementations of Comparator::IsSameLengthImmediateSuccessor has been updated to work around a design bug in `auto_prefix_mode`.
|
||||
* The API documentation for `auto_prefix_mode` now notes some corner cases in which it returns different results than `total_order_seek`, due to design bugs that are not easily fixed. Users using built-in comparators and keys at least the size of a fixed prefix length are not affected.
|
||||
* Obsoleted the NUM_DATA_BLOCKS_READ_PER_LEVEL stat and introduced the NUM_LEVEL_READ_PER_MULTIGET and MULTIGET_COROUTINE_COUNT stats
|
||||
* Introduced `WriteOptions::protection_bytes_per_key`, which can be used to enable key-value integrity protection for live updates.
|
||||
|
||||
### New Features
|
||||
* Add FileSystem::ReadAsync API in io_tracing
|
||||
* Add blob garbage collection parameters `blob_garbage_collection_policy` and `blob_garbage_collection_age_cutoff` to both force-enable and force-disable GC, as well as selectively override age cutoff when using CompactRange.
|
||||
* Add an extra sanity check in `GetSortedWalFiles()` (also used by `GetLiveFilesStorageInfo()`, `BackupEngine`, and `Checkpoint`) to reduce risk of successfully created backup or checkpoint failing to open because of missing WAL file.
|
||||
* Add a new column family option `blob_file_starting_level` to enable writing blob files during flushes and compactions starting from the specified LSM tree level.
|
||||
* Add support for timestamped snapshots (#9879)
|
||||
* Provide support for AbortIO in posix to cancel submitted asynchronous requests using io_uring.
|
||||
* Add support for rate-limiting batched `MultiGet()` APIs
|
||||
* Added several new tickers, perf context statistics, and DB properties to BlobDB
|
||||
* Added new DB properties "rocksdb.blob-cache-capacity", "rocksdb.blob-cache-usage", "rocksdb.blob-cache-pinned-usage" to show blob cache usage.
|
||||
* Added new perf context statistics `blob_cache_hit_count`, `blob_read_count`, `blob_read_byte`, `blob_read_time`, `blob_checksum_time` and `blob_decompress_time`.
|
||||
* Added new tickers `BLOB_DB_CACHE_MISS`, `BLOB_DB_CACHE_HIT`, `BLOB_DB_CACHE_ADD`, `BLOB_DB_CACHE_ADD_FAILURES`, `BLOB_DB_CACHE_BYTES_READ` and `BLOB_DB_CACHE_BYTES_WRITE`.
|
||||
|
||||
### Behavior changes
|
||||
* DB::Open(), DB::OpenAsSecondary() will fail if a Logger cannot be created (#9984)
|
||||
* DB::Write does not hold global `mutex_` if this db instance does not need to switch wal and mem-table (#7516).
|
||||
* Removed support for reading Bloom filters using obsolete block-based filter format. (Support for writing such filters was dropped in 7.0.) For good read performance on old DBs using these filters, a full compaction is required.
|
||||
* Per KV checksum in write batch is verified before a write batch is written to WAL to detect any corruption to the write batch (#10114).
|
||||
|
||||
### Performance Improvements
|
||||
* When compiled with folly (Meta-internal integration; experimental in open source build), improve the locking performance (CPU efficiency) of LRUCache by using folly DistributedMutex in place of standard mutex.
|
||||
|
||||
## 7.3.0 (05/20/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where manual flush would block forever even though flush options had wait=false.
|
||||
* Fixed a bug where RocksDB could corrupt DBs with `avoid_flush_during_recovery == true` by removing valid WALs, leading to `Status::Corruption` with message like "SST file is ahead of WALs" when attempting to reopen.
|
||||
* Fixed a bug in async_io path where incorrect length of data is read by FilePrefetchBuffer if data is consumed from two populated buffers and request for more data is sent.
|
||||
* Fixed a CompactionFilter bug. Compaction filter used to use `Delete` to remove keys, even if the keys should be removed with `SingleDelete`. Mixing `Delete` and `SingleDelete` may cause undefined behavior.
|
||||
* Fixed a bug in `WritableFileWriter::WriteDirect` and `WritableFileWriter::WriteDirectWithChecksum`. The rate_limiter_priority specified in ReadOptions was not passed to the RateLimiter when requesting a token.
|
||||
* Fixed a bug which might cause process crash when I/O error happens when reading an index block in MultiGet().
|
||||
|
||||
### New Features
|
||||
* DB::GetLiveFilesStorageInfo is ready for production use.
|
||||
* Add new stats PREFETCHED_BYTES_DISCARDED which records number of prefetched bytes discarded by RocksDB FilePrefetchBuffer on destruction and POLL_WAIT_MICROS records wait time for FS::Poll API completion.
|
||||
* RemoteCompaction supports table_properties_collector_factories override on compaction worker.
|
||||
* Start tracking SST unique id in MANIFEST, which will be used to verify with SST properties during DB open to make sure the SST file is not overwritten or misplaced. A db option `verify_sst_unique_id_in_manifest` is introduced to enable/disable the verification, if enabled all SST files will be opened during DB-open to verify the unique id (default is false), so it's recommended to use it with `max_open_files = -1` to pre-open the files.
|
||||
* Added the ability to concurrently read data blocks from multiple files in a level in batched MultiGet. This can be enabled by setting the async_io option in ReadOptions. Using this feature requires a FileSystem that supports ReadAsync (PosixFileSystem is not supported yet for this), and for RocksDB to be compiled with folly and c++20.
|
||||
* Charge memory usage of file metadata. RocksDB holds one file metadata structure in-memory per on-disk table file. If an operation reserving memory for file metadata exceeds the avaible space left in the block
|
||||
cache at some point (i.e, causing a cache full under `LRUCacheOptions::strict_capacity_limit` = true), creation will fail with `Status::MemoryLimit()`. To opt in this feature, enable charging `CacheEntryRole::kFileMetadata` in `BlockBasedTableOptions::cache_usage_options`.
|
||||
|
||||
### Public API changes
|
||||
* Add rollback_deletion_type_callback to TransactionDBOptions so that write-prepared transactions know whether to issue a Delete or SingleDelete to cancel a previous key written during prior prepare phase. The PR aims to prevent mixing SingleDeletes and Deletes for the same key that can lead to undefined behaviors for write-prepared transactions.
|
||||
* EXPERIMENTAL: Add new API AbortIO in file_system to abort the read requests submitted asynchronously.
|
||||
* CompactionFilter::Decision has a new value: kRemoveWithSingleDelete. If CompactionFilter returns this decision, then CompactionIterator will use `SingleDelete` to mark a key as removed.
|
||||
* Renamed CompactionFilter::Decision::kRemoveWithSingleDelete to kPurge since the latter sounds more general and hides the implementation details of how compaction iterator handles keys.
|
||||
* Added ability to specify functions for Prepare and Validate to OptionsTypeInfo. Added methods to OptionTypeInfo to set the functions via an API. These methods are intended for RocksDB plugin developers for configuration management.
|
||||
* Added a new immutable db options, enforce_single_del_contracts. If set to false (default is true), compaction will NOT fail due to a single delete followed by a delete for the same key. The purpose of this temporay option is to help existing use cases migrate.
|
||||
* Introduce `BlockBasedTableOptions::cache_usage_options` and use that to replace `BlockBasedTableOptions::reserve_table_builder_memory` and `BlockBasedTableOptions::reserve_table_reader_memory`.
|
||||
* Changed `GetUniqueIdFromTableProperties` to return a 128-bit unique identifier, which will be the standard size now. The old functionality (192-bit) is available from `GetExtendedUniqueIdFromTableProperties`. Both functions are no longer "experimental" and are ready for production use.
|
||||
* In IOOptions, mark `prio` as deprecated for future removal.
|
||||
* In `file_system.h`, mark `IOPriority` as deprecated for future removal.
|
||||
* Add an option, `CompressionOptions::use_zstd_dict_trainer`, to indicate whether zstd dictionary trainer should be used for generating zstd compression dictionaries. The default value of this option is true for backward compatibility. When this option is set to false, zstd API `ZDICT_finalizeDictionary` is used to generate compression dictionaries.
|
||||
* Seek API which positions itself every LevelIterator on the correct data block in the correct SST file which can be parallelized if ReadOptions.async_io option is enabled.
|
||||
* Add new stat number_async_seek in PerfContext that indicates number of async calls made by seek to prefetch data.
|
||||
* Add support for user-defined timestamps to read only DB.
|
||||
|
||||
### Bug Fixes
|
||||
* RocksDB calls FileSystem::Poll API during FilePrefetchBuffer destruction which impacts performance as it waits for read requets completion which is not needed anymore. Calling FileSystem::AbortIO to abort those requests instead fixes that performance issue.
|
||||
* Fixed unnecessary block cache contention when queries within a MultiGet batch and across parallel batches access the same data block, which previously could cause severely degraded performance in this unusual case. (In more typical MultiGet cases, this fix is expected to yield a small or negligible performance improvement.)
|
||||
|
||||
### Behavior changes
|
||||
* Enforce the existing contract of SingleDelete so that SingleDelete cannot be mixed with Delete because it leads to undefined behavior. Fix a number of unit tests that violate the contract but happen to pass.
|
||||
* ldb `--try_load_options` default to true if `--db` is specified and not creating a new DB, the user can still explicitly disable that by `--try_load_options=false` (or explicitly enable that by `--try_load_options`).
|
||||
* During Flush write or Compaction write/read, the WriteController is used to determine whether DB writes are stalled or slowed down. The priority (Env::IOPriority) can then be determined accordingly and be passed in IOOptions to the file system.
|
||||
|
||||
### Performance Improvements
|
||||
* Avoid calling malloc_usable_size() in LRU Cache's mutex.
|
||||
* Reduce DB mutex holding time when finding obsolete files to delete. When a file is trivial moved to another level, the internal files will be referenced twice internally and sometimes opened twice too. If a deletion candidate file is not the last reference, we need to destroy the reference and close the file but not deleting the file. Right now we determine it by building a set of all live files. With the improvement, we check the file against all live LSM-tree versions instead.
|
||||
|
||||
## 7.2.0 (04/15/2022)
|
||||
### Bug Fixes
|
||||
* Fixed bug which caused rocksdb failure in the situation when rocksdb was accessible using UNC path
|
||||
* Fixed a race condition when 2PC is disabled and WAL tracking in the MANIFEST is enabled. The race condition is between two background flush threads trying to install flush results, causing a WAL deletion not tracked in the MANIFEST. A future DB open may fail.
|
||||
* Fixed a heap use-after-free race with DropColumnFamily.
|
||||
* Fixed a bug that `rocksdb.read.block.compaction.micros` cannot track compaction stats (#9722).
|
||||
* Fixed `file_type`, `relative_filename` and `directory` fields returned by `GetLiveFilesMetaData()`, which were added in inheriting from `FileStorageInfo`.
|
||||
* Fixed a bug 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 (#9766).
|
||||
* Fix segfault in FilePrefetchBuffer with async_io as it doesn't wait for pending jobs to complete on destruction.
|
||||
* Fix ERROR_HANDLER_AUTORESUME_RETRY_COUNT stat whose value was set wrong in portal.h
|
||||
* Fixed a bug for non-TransactionDB with avoid_flush_during_recovery = true and TransactionDB where in case of crash, min_log_number_to_keep may not change on recovery and persisting a new MANIFEST with advanced log_numbers for some column families, results in "column family inconsistency" error on second recovery. As a solution the corrupted WALs whose numbers are larger than the corrupted wal and smaller than the new WAL will be moved to archive folder.
|
||||
* Fixed a bug in RocksDB DB::Open() which may creates and writes to two new MANIFEST files even before recovery succeeds. Now writes to MANIFEST are persisted only after recovery is successful.
|
||||
|
||||
### New Features
|
||||
* For db_bench when --seed=0 or --seed is not set then it uses the current time as the seed value. Previously it used the value 1000.
|
||||
* For db_bench when --benchmark lists multiple tests and each test uses a seed for a RNG then the seeds across tests will no longer be repeated.
|
||||
* Added an option to dynamically charge an updating estimated memory usage of block-based table reader to block cache if block cache available. To enable this feature, set `BlockBasedTableOptions::reserve_table_reader_memory = true`.
|
||||
* Add new stat ASYNC_READ_BYTES that calculates number of bytes read during async read call and users can check if async code path is being called by RocksDB internal automatic prefetching for sequential reads.
|
||||
* Enable async prefetching if ReadOptions.readahead_size is set along with ReadOptions.async_io in FilePrefetchBuffer.
|
||||
* Add event listener support on remote compaction compactor side.
|
||||
* Added a dedicated integer DB property `rocksdb.live-blob-file-garbage-size` that exposes the total amount of garbage in the blob files in the current version.
|
||||
* RocksDB does internal auto prefetching if it notices sequential reads. It starts with readahead size `initial_auto_readahead_size` which now can be configured through BlockBasedTableOptions.
|
||||
* Add a merge operator that allows users to register specific aggregation function so that they can does aggregation using different aggregation types for different keys. See comments in include/rocksdb/utilities/agg_merge.h for actual usage. The feature is experimental and the format is subject to change and we won't provide a migration tool.
|
||||
* Meta-internal / Experimental: Improve CPU performance by replacing many uses of std::unordered_map with folly::F14FastMap when RocksDB is compiled together with Folly.
|
||||
* Experimental: Add CompressedSecondaryCache, a concrete implementation of rocksdb::SecondaryCache, that integrates with compression libraries (e.g. LZ4) to hold compressed blocks.
|
||||
|
||||
### Behavior changes
|
||||
* Disallow usage of commit-time-write-batch for write-prepared/write-unprepared transactions if TransactionOptions::use_only_the_last_commit_time_batch_for_recovery is false to prevent two (or more) uncommitted versions of the same key in the database. Otherwise, bottommost compaction may violate the internal key uniqueness invariant of SSTs if the sequence numbers of both internal keys are zeroed out (#9794).
|
||||
* Make DB::GetUpdatesSince() return NotSupported early for write-prepared/write-unprepared transactions, as the API contract indicates.
|
||||
|
||||
### Public API changes
|
||||
* Exposed APIs to examine results of block cache stats collections in a structured way. In particular, users of `GetMapProperty()` with property `kBlockCacheEntryStats` can now use the functions in `BlockCacheEntryStatsMapKeys` to find stats in the map.
|
||||
* Add `fail_if_not_bottommost_level` to IngestExternalFileOptions so that ingestion will fail if the file(s) cannot be ingested to the bottommost level.
|
||||
* Add output parameter `is_in_sec_cache` to `SecondaryCache::Lookup()`. It is to indicate whether the handle is possibly erased from the secondary cache after the Lookup.
|
||||
|
||||
## 7.1.0 (03/23/2022)
|
||||
### New Features
|
||||
* Allow WriteBatchWithIndex to index a WriteBatch that includes keys with user-defined timestamps. The index itself does not have timestamp.
|
||||
* Add support for user-defined timestamps to write-committed transaction without API change. The `TransactionDB` layer APIs do not allow timestamps because we require that all user-defined-timestamps-aware operations go through the `Transaction` APIs.
|
||||
* Added BlobDB options to `ldb`
|
||||
* `BlockBasedTableOptions::detect_filter_construct_corruption` can now be dynamically configured using `DB::SetOptions`.
|
||||
* Automatically recover from retryable read IO errors during backgorund flush/compaction.
|
||||
* Experimental support for preserving file Temperatures through backup and restore, and for updating DB metadata for outside changes to file Temperature (`UpdateManifestForFilesState` or `ldb update_manifest --update_temperatures`).
|
||||
* Experimental support for async_io in ReadOptions which is used by FilePrefetchBuffer to prefetch some of the data asynchronously, if reads are sequential and auto readahead is enabled by rocksdb internally.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a major performance bug in which Bloom filters generated by pre-7.0 releases are not read by early 7.0.x releases (and vice-versa) due to changes to FilterPolicy::Name() in #9590. This can severely impact read performance and read I/O on upgrade or downgrade with existing DB, but not data correctness.
|
||||
* Fixed a data race on `versions_` between `DBImpl::ResumeImpl()` and threads waiting for recovery to complete (#9496)
|
||||
* Fixed a bug caused by race among flush, incoming writes and taking snapshots. Queries to snapshots created with these race condition can return incorrect result, e.g. resurfacing deleted data.
|
||||
* Fixed a bug that DB flush uses `options.compression` even `options.compression_per_level` is set.
|
||||
* Fixed a bug that DisableManualCompaction may assert when disable an unscheduled manual compaction.
|
||||
* Fix a race condition when cancel manual compaction with `DisableManualCompaction`. Also DB close can cancel the manual compaction thread.
|
||||
* Fixed a potential timer crash when open close DB concurrently.
|
||||
* Fixed a race condition for `alive_log_files_` in non-two-write-queues mode. The race is between the write_thread_ in WriteToWAL() and another thread executing `FindObsoleteFiles()`. The race condition will be caught if `__glibcxx_requires_nonempty` is enabled.
|
||||
* Fixed a bug that `Iterator::Refresh()` reads stale keys after DeleteRange() performed.
|
||||
* Fixed a race condition when disable and re-enable manual compaction.
|
||||
* Fixed automatic error recovery failure in atomic flush.
|
||||
* Fixed a race condition when mmaping a WritableFile on POSIX.
|
||||
|
||||
### Public API changes
|
||||
* Added pure virtual FilterPolicy::CompatibilityName(), which is needed for fixing major performance bug involving FilterPolicy naming in SST metadata without affecting Customizable aspect of FilterPolicy. This change only affects those with their own custom or wrapper FilterPolicy classes.
|
||||
* `options.compression_per_level` is dynamically changeable with `SetOptions()`.
|
||||
* Added `WriteOptions::rate_limiter_priority`. When set to something other than `Env::IO_TOTAL`, the internal rate limiter (`DBOptions::rate_limiter`) will be charged at the specified priority for writes associated with the API to which the `WriteOptions` was provided. Currently the support covers automatic WAL flushes, which happen during live updates (`Put()`, `Write()`, `Delete()`, etc.) when `WriteOptions::disableWAL == false` and `DBOptions::manual_wal_flush == false`.
|
||||
* Add DB::OpenAndTrimHistory API. This API will open DB and trim data to the timestamp specified by trim_ts (The data with timestamp larger than specified trim bound will be removed). This API should only be used at a timestamp-enabled column families recovery. If the column family doesn't have timestamp enabled, this API won't trim any data on that column family. This API is not compatible with avoid_flush_during_recovery option.
|
||||
* Remove BlockBasedTableOptions.hash_index_allow_collision which already takes no effect.
|
||||
|
||||
## 7.0.0 (02/20/2022)
|
||||
### Bug Fixes
|
||||
* Fixed a major bug in which batched MultiGet could return old values for keys deleted by DeleteRange when memtable Bloom filter is enabled (memtable_prefix_bloom_size_ratio > 0). (The fix includes a substantial MultiGet performance improvement in the unusual case of both memtable_whole_key_filtering and prefix_extractor.)
|
||||
* Fixed more cases of EventListener::OnTableFileCreated called with OK status, file_size==0, and no SST file kept. Now the status is Aborted.
|
||||
* Fixed a read-after-free bug in `DB::GetMergeOperands()`.
|
||||
* Fix a data loss bug for 2PC write-committed transaction caused by concurrent transaction commit and memtable switch (#9571).
|
||||
* Fixed NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL, NUM_DATA_BLOCKS_READ_PER_LEVEL, and NUM_SST_READ_PER_LEVEL stats to be reported once per MultiGet batch per level.
|
||||
|
||||
### Performance Improvements
|
||||
* Mitigated the overhead of building the file location hash table used by the online LSM tree consistency checks, which can improve performance for certain workloads (see #9351).
|
||||
* Switched to using a sorted `std::vector` instead of `std::map` for storing the metadata objects for blob files, which can improve performance for certain workloads, especially when the number of blob files is high.
|
||||
* DisableManualCompaction() doesn't have to wait scheduled manual compaction to be executed in thread-pool to cancel the job.
|
||||
|
||||
### Public API changes
|
||||
* Require C++17 compatible compiler (GCC >= 7, Clang >= 5, Visual Studio >= 2017) for compiling RocksDB and any code using RocksDB headers. See #9388.
|
||||
* Added `ReadOptions::rate_limiter_priority`. When set to something other than `Env::IO_TOTAL`, the internal rate limiter (`DBOptions::rate_limiter`) will be charged at the specified priority for file reads associated with the API to which the `ReadOptions` was provided.
|
||||
* Remove HDFS support from main repo.
|
||||
* Remove librados support from main repo.
|
||||
* Remove obsolete backupable_db.h and type alias `BackupableDBOptions`. Use backup_engine.h and `BackupEngineOptions`. Similar renamings are in the C and Java APIs.
|
||||
* Removed obsolete utility_db.h and `UtilityDB::OpenTtlDB`. Use db_ttl.h and `DBWithTTL::Open`.
|
||||
* Remove deprecated API DB::AddFile from main repo.
|
||||
* Remove deprecated API ObjectLibrary::Register() and the (now obsolete) Regex public API. Use ObjectLibrary::AddFactory() with PatternEntry instead.
|
||||
* Remove deprecated option DBOption::table_cache_remove_scan_count_limit.
|
||||
* Remove deprecated API AdvancedColumnFamilyOptions::soft_rate_limit.
|
||||
* Remove deprecated API AdvancedColumnFamilyOptions::hard_rate_limit.
|
||||
* Remove deprecated API DBOption::base_background_compactions.
|
||||
* Remove deprecated API DBOptions::purge_redundant_kvs_while_flush.
|
||||
* Remove deprecated overloads of API DB::CompactRange.
|
||||
* Remove deprecated option DBOptions::skip_log_error_on_recovery.
|
||||
* Remove ReadOptions::iter_start_seqnum which has been deprecated.
|
||||
* Remove DBOptions::preserved_deletes and DB::SetPreserveDeletesSequenceNumber().
|
||||
* Remove deprecated API AdvancedColumnFamilyOptions::rate_limit_delay_max_milliseconds.
|
||||
* Removed timestamp from WriteOptions. Accordingly, added to DB APIs Put, Delete, SingleDelete, etc. accepting an additional argument 'timestamp'. Added Put, Delete, SingleDelete, etc to WriteBatch accepting an additional argument 'timestamp'. Removed WriteBatch::AssignTimestamps(vector<Slice>) API. Renamed WriteBatch::AssignTimestamp() to WriteBatch::UpdateTimestamps() with clarified comments.
|
||||
* Changed type of cache buffer passed to `Cache::CreateCallback` from `void*` to `const void*`.
|
||||
* Significant updates to FilterPolicy-related APIs and configuration:
|
||||
* Remove public API support for deprecated, inefficient block-based filter (use_block_based_builder=true).
|
||||
* Old code and configuration strings that would enable it now quietly enable full filters instead, though any built-in FilterPolicy can still read block-based filters. This includes changing the longstanding default behavior of the Java API.
|
||||
* Remove deprecated FilterPolicy::CreateFilter() and FilterPolicy::KeyMayMatch()
|
||||
* Remove `rocksdb_filterpolicy_create()` from C API, as the only C API support for custom filter policies is now obsolete.
|
||||
* If temporary memory usage in full filter creation is a problem, consider using partitioned filters, smaller SST files, or setting reserve_table_builder_memory=true.
|
||||
* Remove support for "filter_policy=experimental_ribbon" configuration
|
||||
string. Use something like "filter_policy=ribbonfilter:10" instead.
|
||||
* Allow configuration string like "filter_policy=bloomfilter:10" without
|
||||
bool, to minimize acknowledgement of obsolete block-based filter.
|
||||
* Made FilterPolicy Customizable. Configuration of filter_policy is now accurately saved in OPTIONS file and can be loaded with LoadOptionsFromFile. (Loading an OPTIONS file generated by a previous version only enables reading and using existing filters, not generating new filters. Previously, no filter_policy would be configured from a saved OPTIONS file.)
|
||||
* Change meaning of nullptr return from GetBuilderWithContext() from "use
|
||||
block-based filter" to "generate no filter in this case."
|
||||
* Also, when user specifies bits_per_key < 0.5, we now round this down
|
||||
to "no filter" because we expect a filter with >= 80% FP rate is
|
||||
unlikely to be worth the CPU cost of accessing it (esp with
|
||||
cache_index_and_filter_blocks=1 or partition_filters=1).
|
||||
* bits_per_key >= 0.5 and < 1.0 is still rounded up to 1.0 (for 62% FP
|
||||
rate)
|
||||
* Remove class definitions for FilterBitsBuilder and FilterBitsReader from
|
||||
public API, so these can evolve more easily as implementation details.
|
||||
Custom FilterPolicy can still decide what kind of built-in filter to use
|
||||
under what conditions.
|
||||
* Also removed deprecated functions
|
||||
* FilterPolicy::GetFilterBitsBuilder()
|
||||
* NewExperimentalRibbonFilterPolicy()
|
||||
* Remove default implementations of
|
||||
* FilterPolicy::GetBuilderWithContext()
|
||||
* Remove default implementation of Name() from FileSystemWrapper.
|
||||
* Rename `SizeApproximationOptions.include_memtabtles` to `SizeApproximationOptions.include_memtables`.
|
||||
* Remove deprecated option DBOptions::max_mem_compaction_level.
|
||||
* Return Status::InvalidArgument from ObjectRegistry::NewObject if a factory exists but the object ould not be created (returns NotFound if the factory is missing).
|
||||
* Remove deprecated overloads of API DB::GetApproximateSizes.
|
||||
* Remove deprecated option DBOptions::new_table_reader_for_compaction_inputs.
|
||||
* Add Transaction::SetReadTimestampForValidation() and Transaction::SetCommitTimestamp(). Default impl returns NotSupported().
|
||||
* Add support for decimal patterns to ObjectLibrary::PatternEntry
|
||||
* Remove deprecated remote compaction APIs `CompactionService::Start()` and `CompactionService::WaitForComplete()`. Please use `CompactionService::StartV2()`, `CompactionService::WaitForCompleteV2()` instead, which provides the same information plus extra data like priority, db_id, etc.
|
||||
* `ColumnFamilyOptions::OldDefaults` and `DBOptions::OldDefaults` are marked deprecated, as they are no longer maintained.
|
||||
* Add subcompaction callback APIs: `OnSubcompactionBegin()` and `OnSubcompactionCompleted()`.
|
||||
* Add file Temperature information to `FileOperationInfo` in event listener API.
|
||||
* Change the type of SizeApproximationFlags from enum to enum class. Also update the signature of DB::GetApproximateSizes API from uint8_t to SizeApproximationFlags.
|
||||
* Add Temperature hints information from RocksDB in API `NewSequentialFile()`. backup and checkpoint operations need to open the source files with `NewSequentialFile()`, which will have the temperature hints. Other operations are not covered.
|
||||
|
||||
### Behavior Changes
|
||||
* Disallow the combination of DBOptions.use_direct_io_for_flush_and_compaction == true and DBOptions.writable_file_max_buffer_size == 0. This combination can cause WritableFileWriter::Append() to loop forever, and it does not make much sense in direct IO.
|
||||
* `ReadOptions::total_order_seek` no longer affects `DB::Get()`. The original motivation for this interaction has been obsolete since RocksDB has been able to detect whether the current prefix extractor is compatible with that used to generate table files, probably RocksDB 5.14.0.
|
||||
|
||||
## New Features
|
||||
* Introduced an option `BlockBasedTableOptions::detect_filter_construct_corruption` for detecting corruption during Bloom Filter (format_version >= 5) and Ribbon Filter construction.
|
||||
* Improved the SstDumpTool to read the comparator from table properties and use it to read the SST File.
|
||||
* Extended the column family statistics in the info log so the total amount of garbage in the blob files and the blob file space amplification factor are also logged. Also exposed the blob file space amp via the `rocksdb.blob-stats` DB property.
|
||||
* Introduced the API rocksdb_create_dir_if_missing in c.h that calls underlying file system's CreateDirIfMissing API to create the directory.
|
||||
* Added last level and non-last level read statistics: `LAST_LEVEL_READ_*`, `NON_LAST_LEVEL_READ_*`.
|
||||
* Experimental: Add support for new APIs ReadAsync in FSRandomAccessFile that reads the data asynchronously and Poll API in FileSystem that checks if requested read request has completed or not. ReadAsync takes a callback function. Poll API checks for completion of read IO requests and should call callback functions to indicate completion of read requests.
|
||||
|
||||
## 6.29.0 (01/21/2022)
|
||||
Note: The next release will be major release 7.0. See https://github.com/facebook/rocksdb/issues/9390 for more info.
|
||||
### Public API change
|
||||
* Added values to `TraceFilterType`: `kTraceFilterIteratorSeek`, `kTraceFilterIteratorSeekForPrev`, and `kTraceFilterMultiGet`. They can be set in `TraceOptions` to filter out the operation types after which they are named.
|
||||
* Added `TraceOptions::preserve_write_order`. When enabled it guarantees write records are traced in the same order they are logged to WAL and applied to the DB. By default it is disabled (false) to match the legacy behavior and prevent regression.
|
||||
* Made the Env class extend the Customizable class. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* `Options::OldDefaults` is marked deprecated, as it is no longer maintained.
|
||||
* Add ObjectLibrary::AddFactory and ObjectLibrary::PatternEntry classes. This method and associated class are the preferred mechanism for registering factories with the ObjectLibrary going forward. The ObjectLibrary::Register method, which uses regular expressions and may be problematic, is deprecated and will be in a future release.
|
||||
* Changed `BlockBasedTableOptions::block_size` from `size_t` to `uint64_t`.
|
||||
* Added API warning against using `Iterator::Refresh()` together with `DB::DeleteRange()`, which are incompatible and have always risked causing the refreshed iterator to return incorrect results.
|
||||
* Made `AdvancedColumnFamilyOptions.bottommost_temperature` dynamically changeable with `SetOptions()`.
|
||||
|
||||
### Behavior Changes
|
||||
* `DB::DestroyColumnFamilyHandle()` will return Status::InvalidArgument() if called with `DB::DefaultColumnFamily()`.
|
||||
* On 32-bit platforms, mmap reads are no longer quietly disabled, just discouraged.
|
||||
|
||||
### New Features
|
||||
* Added `Options::DisableExtraChecks()` that can be used to improve peak write performance by disabling checks that should not be necessary in the absence of software logic errors or CPU+memory hardware errors. (Default options are slowly moving toward some performance overheads for extra correctness checking.)
|
||||
|
||||
### Performance Improvements
|
||||
* Improved read performance when a prefix extractor is used (Seek, Get, MultiGet), even compared to version 6.25 baseline (see bug fix below), by optimizing the common case of prefix extractor compatible with table file and unchanging.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a bug that FlushMemTable may return ok even flush not succeed.
|
||||
* Fixed a bug of Sync() and Fsync() not using `fcntl(F_FULLFSYNC)` on OS X and iOS.
|
||||
* Fixed a significant performance regression in version 6.26 when a prefix extractor is used on the read path (Seek, Get, MultiGet). (Excessive time was spent in SliceTransform::AsString().)
|
||||
* Fixed a race condition in SstFileManagerImpl error recovery code that can cause a crash during process shutdown.
|
||||
|
||||
### New Features
|
||||
* Added RocksJava support for MacOS universal binary (ARM+x86)
|
||||
|
||||
## 6.28.0 (2021-12-17)
|
||||
### New Features
|
||||
* Introduced 'CommitWithTimestamp' as a new tag. Currently, there is no API for user to trigger a write with this tag to the WAL. This is part of the efforts to support write-commited transactions with user-defined timestamps.
|
||||
* Introduce SimulatedHybridFileSystem which can help simulating HDD latency in db_bench. Tiered Storage latency simulation can be enabled using -simulate_hybrid_fs_file (note that it doesn't work if db_bench is interrupted in the middle). -simulate_hdd can also be used to simulate all files on HDD.
|
||||
|
||||
### Bug Fixes
|
||||
* Fixed a bug in rocksdb automatic implicit prefetching which got broken because of new feature adaptive_readahead and internal prefetching got disabled when iterator moves from one file to next.
|
||||
* Fixed a bug in TableOptions.prepopulate_block_cache which causes segmentation fault when used with TableOptions.partition_filters = true and TableOptions.cache_index_and_filter_blocks = true.
|
||||
* Fixed a bug affecting custom memtable factories which are not registered with the `ObjectRegistry`. The bug could result in failure to save the OPTIONS file.
|
||||
* Fixed a bug causing two duplicate entries to be appended to a file opened in non-direct mode and tracked by `FaultInjectionTestFS`.
|
||||
* Fixed a bug in TableOptions.prepopulate_block_cache to support block-based filters also.
|
||||
* Block cache keys no longer use `FSRandomAccessFile::GetUniqueId()` (previously used when available), so a filesystem recycling unique ids can no longer lead to incorrect result or crash (#7405). For files generated by RocksDB >= 6.24, the cache keys are stable across DB::Open and DB directory move / copy / import / export / migration, etc. Although collisions are still theoretically possible, they are (a) impossible in many common cases, (b) not dependent on environmental factors, and (c) much less likely than a CPU miscalculation while executing RocksDB.
|
||||
* Fixed a bug in C bindings causing iterator to return incorrect result (#9343).
|
||||
|
||||
### Behavior Changes
|
||||
* MemTableList::TrimHistory now use allocated bytes when max_write_buffer_size_to_maintain > 0(default in TrasactionDB, introduced in PR#5022) Fix #8371.
|
||||
|
||||
### Public API change
|
||||
* Extend WriteBatch::AssignTimestamp and AssignTimestamps API so that both functions can accept an optional `checker` argument that performs additional checking on timestamp sizes.
|
||||
* Introduce a new EventListener callback that will be called upon the end of automatic error recovery.
|
||||
* Add IncreaseFullHistoryTsLow API so users can advance each column family's full_history_ts_low seperately.
|
||||
* Add GetFullHistoryTsLow API so users can query current full_history_low value of specified column family.
|
||||
|
||||
### Performance Improvements
|
||||
* Replaced map property `TableProperties::properties_offsets` with uint64_t property `external_sst_file_global_seqno_offset` to save table properties's memory.
|
||||
* Block cache accesses are faster by RocksDB using cache keys of fixed size (16 bytes).
|
||||
|
||||
### Java API Changes
|
||||
* Removed Java API `TableProperties.getPropertiesOffsets()` as it exposed internal details to external users.
|
||||
|
||||
## 6.27.0 (2021-11-19)
|
||||
### New Features
|
||||
* Added new ChecksumType kXXH3 which is faster than kCRC32c on almost all x86\_64 hardware.
|
||||
* Added a new online consistency check for BlobDB which validates that the number/total size of garbage blobs does not exceed the number/total size of all blobs in any given blob file.
|
||||
* Provided support for tracking per-sst user-defined timestamp information in MANIFEST.
|
||||
* Added new option "adaptive_readahead" in ReadOptions. For iterators, RocksDB does auto-readahead on noticing sequential reads and by enabling this option, readahead_size of current file (if reads are sequential) will be carried forward to next file instead of starting from the scratch at each level (except L0 level files). If reads are not sequential it will fall back to 8KB. This option is applicable only for RocksDB internal prefetch buffer and isn't supported with underlying file system prefetching.
|
||||
* Added the read count and read bytes related stats to Statistics for tiered storage hot, warm, and cold file reads.
|
||||
* Added an option to dynamically charge an updating estimated memory usage of block-based table building to block cache if block cache available. It currently only includes charging memory usage of constructing (new) Bloom Filter and Ribbon Filter to block cache. To enable this feature, set `BlockBasedTableOptions::reserve_table_builder_memory = true`.
|
||||
* Add a new API OnIOError in listener.h that notifies listeners when an IO error occurs during FileSystem operation along with filename, status etc.
|
||||
* Added compaction readahead support for blob files to the integrated BlobDB implementation, which can improve compaction performance when the database resides on higher-latency storage like HDDs or remote filesystems. Readahead can be configured using the column family option `blob_compaction_readahead_size`.
|
||||
|
||||
### Bug Fixes
|
||||
* Prevent a `CompactRange()` with `CompactRangeOptions::change_level == true` from possibly causing corruption to the LSM state (overlapping files within a level) when run in parallel with another manual compaction. Note that setting `force_consistency_checks == true` (the default) would cause the DB to enter read-only mode in this scenario and return `Status::Corruption`, rather than committing any corruption.
|
||||
* Fixed a bug in CompactionIterator when write-prepared transaction is used. A released earliest write conflict snapshot may cause assertion failure in dbg mode and unexpected key in opt mode.
|
||||
* Fix ticker WRITE_WITH_WAL("rocksdb.write.wal"), this bug is caused by a bad extra `RecordTick(stats_, WRITE_WITH_WAL)` (at 2 place), this fix remove the extra `RecordTick`s and fix the corresponding test case.
|
||||
* EventListener::OnTableFileCreated was previously called with OK status and file_size==0 in cases of no SST file contents written (because there was no content to add) and the empty file deleted before calling the listener. Now the status is Aborted.
|
||||
* Fixed a bug in CompactionIterator when write-preared transaction is used. Releasing earliest_snapshot during compaction may cause a SingleDelete to be output after a PUT of the same user key whose seq has been zeroed.
|
||||
* Added input sanitization on negative bytes passed into `GenericRateLimiter::Request`.
|
||||
* Fixed an assertion failure in CompactionIterator when write-prepared transaction is used. We prove that certain operations can lead to a Delete being followed by a SingleDelete (same user key). We can drop the SingleDelete.
|
||||
* Fixed a bug of timestamp-based GC which can cause all versions of a key under full_history_ts_low to be dropped. This bug will be triggered when some of the ikeys' timestamps are lower than full_history_ts_low, while others are newer.
|
||||
* In some cases outside of the DB read and compaction paths, SST block checksums are now checked where they were not before.
|
||||
* Explicitly check for and disallow the `BlockBasedTableOptions` if insertion into one of {`block_cache`, `block_cache_compressed`, `persistent_cache`} can show up in another of these. (RocksDB expects to be able to use the same key for different physical data among tiers.)
|
||||
* Users who configured a dedicated thread pool for bottommost compactions by explicitly adding threads to the `Env::Priority::BOTTOM` pool will no longer see RocksDB schedule automatic compactions exceeding the DB's compaction concurrency limit. For details on per-DB compaction concurrency limit, see API docs of `max_background_compactions` and `max_background_jobs`.
|
||||
* Fixed a bug of background flush thread picking more memtables to flush and prematurely advancing column family's log_number.
|
||||
* Fixed an assertion failure in ManifestTailer.
|
||||
* Fixed a bug that could, with WAL enabled, cause backups, checkpoints, and `GetSortedWalFiles()` to fail randomly with an error like `IO error: 001234.log: No such file or directory`
|
||||
|
||||
### Behavior Changes
|
||||
* `NUM_FILES_IN_SINGLE_COMPACTION` was only counting the first input level files, now it's including all input files.
|
||||
* `TransactionUtil::CheckKeyForConflicts` can also perform conflict-checking based on user-defined timestamps in addition to sequence numbers.
|
||||
* Removed `GenericRateLimiter`'s minimum refill bytes per period previously enforced.
|
||||
|
||||
### Public API change
|
||||
* When options.ttl is used with leveled compaction with compactinon priority kMinOverlappingRatio, files exceeding half of TTL value will be prioritized more, so that by the time TTL is reached, fewer extra compactions will be scheduled to clear them up. At the same time, when compacting files with data older than half of TTL, output files may be cut off based on those files' boundaries, in order for the early TTL compaction to work properly.
|
||||
* Made FileSystem and RateLimiter extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* Clarified in API comments that RocksDB is not exception safe for callbacks and custom extensions. An exception propagating into RocksDB can lead to undefined behavior, including data loss, unreported corruption, deadlocks, and more.
|
||||
* Marked `WriteBufferManager` as `final` because it is not intended for extension.
|
||||
* Removed unimportant implementation details from table_properties.h
|
||||
* Add API `FSDirectory::FsyncWithDirOptions()`, which provides extra information like directory fsync reason in `DirFsyncOptions`. File system like btrfs is using that to skip directory fsync for creating a new file, or when renaming a file, fsync the target file instead of the directory, which improves the `DB::Open()` speed by ~20%.
|
||||
* `DB::Open()` is not going be blocked by obsolete file purge if `DBOptions::avoid_unnecessary_blocking_io` is set to true.
|
||||
* In builds where glibc provides `gettid()`, info log ("LOG" file) lines now print a system-wide thread ID from `gettid()` instead of the process-local `pthread_self()`. For all users, the thread ID format is changed from hexadecimal to decimal integer.
|
||||
* In builds where glibc provides `pthread_setname_np()`, the background thread names no longer contain an ID suffix. For example, "rocksdb:bottom7" (and all other threads in the `Env::Priority::BOTTOM` pool) are now named "rocksdb:bottom". Previously large thread pools could breach the name size limit (e.g., naming "rocksdb:bottom10" would fail).
|
||||
* Deprecating `ReadOptions::iter_start_seqnum` and `DBOptions::preserve_deletes`, please try using user defined timestamp feature instead. The options will be removed in a future release, currently it logs a warning message when using.
|
||||
|
||||
### Performance Improvements
|
||||
* Released some memory related to filter construction earlier in `BlockBasedTableBuilder` for `FullFilter` and `PartitionedFilter` case (#9070)
|
||||
|
||||
### Behavior Changes
|
||||
* `NUM_FILES_IN_SINGLE_COMPACTION` was only counting the first input level files, now it's including all input files.
|
||||
* Fix builds for some platforms.
|
||||
|
||||
## 6.26.0 (2021-10-20)
|
||||
### Bug Fixes
|
||||
@@ -550,7 +14,6 @@ Note: The next release will be major release 7.0. See https://github.com/faceboo
|
||||
* Fixed a bug where stalled writes would remain stalled forever after the user calls `WriteBufferManager::SetBufferSize()` with `new_size == 0` to dynamically disable memory limiting.
|
||||
* Make `DB::close()` thread-safe.
|
||||
* Fix a bug in atomic flush where one bg flush thread will wait forever for a preceding bg flush thread to commit its result to MANIFEST but encounters an error which is mapped to a soft error (DB not stopped).
|
||||
* Fix a bug in `BackupEngine` where some internal callers of `GenericRateLimiter::Request()` do not honor `bytes <= GetSingleBurstBytes()`.
|
||||
|
||||
### New Features
|
||||
* Print information about blob files when using "ldb list_live_files_metadata"
|
||||
@@ -1967,7 +1430,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Added a new way to report QPS from db_bench (check out --report_file and --report_interval_seconds)
|
||||
* Added a cache for individual rows. See DBOptions::row_cache for more info.
|
||||
* Several new features on EventListener (see include/rocksdb/listener.h):
|
||||
- OnCompactionCompleted() now returns per-compaction job statistics, defined in include/rocksdb/compaction_job_stats.h.
|
||||
- OnCompationCompleted() now returns per-compaction job statistics, defined in include/rocksdb/compaction_job_stats.h.
|
||||
- Added OnTableFileCreated() and OnTableFileDeleted().
|
||||
* Add compaction_options_universal.enable_trivial_move to true, to allow trivial move while performing universal compaction. Trivial move will happen only when all the input files are non overlapping.
|
||||
|
||||
|
||||
+14
-13
@@ -6,7 +6,7 @@ than release mode.
|
||||
|
||||
RocksDB's library should be able to compile without any dependency installed,
|
||||
although we recommend installing some compression libraries (see below).
|
||||
We do depend on newer gcc/clang with C++17 support (GCC >= 7, Clang >= 5).
|
||||
We do depend on newer gcc/clang with C++11 support.
|
||||
|
||||
There are few options when compiling RocksDB:
|
||||
|
||||
@@ -47,12 +47,10 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
|
||||
* 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.
|
||||
|
||||
## Supported platforms
|
||||
|
||||
* **Linux - Ubuntu**
|
||||
* Upgrade your gcc to version at least 7 to get C++17 support.
|
||||
* Upgrade your gcc to version at least 4.8 to get C++11 support.
|
||||
* Install gflags. First, try: `sudo apt-get install libgflags-dev`
|
||||
If this doesn't work and you're using Ubuntu, here's a nice tutorial:
|
||||
(http://askubuntu.com/questions/312173/installing-gflags-12-04)
|
||||
@@ -64,7 +62,8 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
* Install zstandard: `sudo apt-get install libzstd-dev`.
|
||||
|
||||
* **Linux - CentOS / RHEL**
|
||||
* Upgrade your gcc to version at least 7 to get C++17 support
|
||||
* Upgrade your gcc to version at least 4.8 to get C++11 support:
|
||||
`yum install gcc48-c++`
|
||||
* Install gflags:
|
||||
|
||||
git clone https://github.com/gflags/gflags.git
|
||||
@@ -114,11 +113,11 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
make && sudo make install
|
||||
|
||||
* **OS X**:
|
||||
* Install latest C++ compiler that supports C++ 17:
|
||||
* Install latest C++ compiler that supports C++ 11:
|
||||
* Update XCode: run `xcode-select --install` (or install it from XCode App's settting).
|
||||
* Install via [homebrew](http://brew.sh/).
|
||||
* If you're first time developer in MacOS, you still need to run: `xcode-select --install` in your command line.
|
||||
* run `brew tap homebrew/versions; brew install gcc7 --use-llvm` to install gcc 7 (or higher).
|
||||
* run `brew tap homebrew/versions; brew install gcc48 --use-llvm` to install gcc 4.8 (or higher).
|
||||
* run `brew install rocksdb`
|
||||
|
||||
* **FreeBSD** (11.01):
|
||||
@@ -161,7 +160,7 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
|
||||
* Install the dependencies for RocksDB:
|
||||
|
||||
pkg_add gmake gflags snappy bzip2 lz4 zstd git jdk bash findutils gnuwatch
|
||||
pkg_add gmake gflags snappy bzip2 lz4 zstd git jdk bash findutils gnuwatch
|
||||
|
||||
* Build RocksDB from source:
|
||||
|
||||
@@ -180,15 +179,16 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
* **iOS**:
|
||||
* 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):
|
||||
* **Windows**:
|
||||
* For building with MS Visual Studio 13 you will need Update 4 installed.
|
||||
* Read and follow the instructions at CMakeLists.txt
|
||||
* Or install via [vcpkg](https://github.com/microsoft/vcpkg)
|
||||
* Or install via [vcpkg](https://github.com/microsoft/vcpkg)
|
||||
* run `vcpkg install rocksdb:x64-windows`
|
||||
|
||||
* **AIX 6.1**
|
||||
* Install AIX Toolbox rpms with gcc
|
||||
* Use these environment variables:
|
||||
|
||||
|
||||
export PORTABLE=1
|
||||
export CC=gcc
|
||||
export AR="ar -X64"
|
||||
@@ -199,9 +199,9 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
export LIBPATH=/opt/freeware/lib
|
||||
export JAVA_HOME=/usr/java8_64
|
||||
export PATH=/opt/freeware/bin:$PATH
|
||||
|
||||
|
||||
* **Solaris Sparc**
|
||||
* Install GCC 7 and higher.
|
||||
* Install GCC 4.8.2 and higher.
|
||||
* Use these environment variables:
|
||||
|
||||
export CC=gcc
|
||||
@@ -210,3 +210,4 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
export EXTRA_LDFLAGS=-m64
|
||||
export PORTABLE=1
|
||||
export PLATFORM_LDFLAGS="-static-libstdc++ -static-libgcc"
|
||||
|
||||
@@ -6,9 +6,7 @@ This is the list of all known third-party language bindings for RocksDB. If some
|
||||
* http://pyrocksdb.readthedocs.org/en/latest/ (unmaintained)
|
||||
* Perl - https://metacpan.org/pod/RocksDB
|
||||
* Node.js - https://npmjs.org/package/rocksdb
|
||||
* Go
|
||||
* https://github.com/linxGnu/grocksdb
|
||||
* https://github.com/tecbot/gorocksdb (unmaintained)
|
||||
* Go - https://github.com/tecbot/gorocksdb
|
||||
* Ruby - http://rubygems.org/gems/rocksdb-ruby
|
||||
* Haskell - https://hackage.haskell.org/package/rocksdb-haskell
|
||||
* PHP - https://github.com/Photonios/rocksdb-php
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
This is the list of all known third-party plugins for RocksDB. If something is missing, please open a pull request to add it.
|
||||
|
||||
* [Dedupfs](https://github.com/ajkr/dedupfs): an example for plugin developers to reference
|
||||
* [HDFS](https://github.com/riversand963/rocksdb-hdfs-env): an Env used for interacting with HDFS. Migrated from main RocksDB repo
|
||||
* [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.
|
||||
@@ -1,6 +1,7 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](https://circleci.com/gh/facebook/rocksdb)
|
||||
[](https://travis-ci.com/github/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/main)
|
||||
[](http://140-211-168-68-openstack.osuosl.org:8080/job/rocksdb)
|
||||
|
||||
@@ -24,7 +25,7 @@ The public interface is in `include/`. Callers should not include or
|
||||
rely on the details of any other header files in this package. Those
|
||||
internal APIs may be changed without warning.
|
||||
|
||||
Questions and discussions are welcome on the [RocksDB Developers Public](https://www.facebook.com/groups/rocksdb.dev/) Facebook group and [email list](https://groups.google.com/g/rocksdb) on Google Groups.
|
||||
Design discussions are conducted in https://www.facebook.com/groups/rocksdb.dev/ and https://rocksdb.slack.com/
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ RocksDBLite is a project focused on mobile use cases, which don't need a lot of
|
||||
|
||||
Some examples of the features disabled by ROCKSDB_LITE:
|
||||
* compiled-in support for LDB tool
|
||||
* No backup engine
|
||||
* No backupable DB
|
||||
* 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
|
||||
|
||||
@@ -121,8 +121,6 @@ LzLabs is using RocksDB as a storage engine in their multi-database distributed
|
||||
## Kafka
|
||||
[Kafka](https://kafka.apache.org/) is an open-source distributed event streaming platform, it uses RocksDB to store state in Kafka Streams: https://www.confluent.io/blog/how-to-tune-rocksdb-kafka-streams-state-stores-performance/.
|
||||
|
||||
## Solana Labs
|
||||
[Solana](https://github.com/solana-labs/solana) is a fast, secure, scalable, and decentralized blockchain. It uses RocksDB as the underlying storage for its ledger store.
|
||||
|
||||
## Others
|
||||
More databases using RocksDB can be found at [dbdb.io](https://dbdb.io/browse?embeds=rocksdb).
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
version: 1.0.{build}
|
||||
|
||||
image: Visual Studio 2019
|
||||
|
||||
environment:
|
||||
JAVA_HOME: C:\Program Files\Java\jdk1.8.0
|
||||
THIRDPARTY_HOME: $(APPVEYOR_BUILD_FOLDER)\thirdparty
|
||||
SNAPPY_HOME: $(THIRDPARTY_HOME)\snappy-1.1.7
|
||||
SNAPPY_INCLUDE: $(SNAPPY_HOME);$(SNAPPY_HOME)\build
|
||||
SNAPPY_LIB_DEBUG: $(SNAPPY_HOME)\build\Debug\snappy.lib
|
||||
SNAPPY_LIB_RELEASE: $(SNAPPY_HOME)\build\Release\snappy.lib
|
||||
LZ4_HOME: $(THIRDPARTY_HOME)\lz4-1.8.3
|
||||
LZ4_INCLUDE: $(LZ4_HOME)\lib
|
||||
LZ4_LIB_DEBUG: $(LZ4_HOME)\visual\VS2010\bin\x64_Debug\liblz4_static.lib
|
||||
LZ4_LIB_RELEASE: $(LZ4_HOME)\visual\VS2010\bin\x64_Release\liblz4_static.lib
|
||||
ZSTD_HOME: $(THIRDPARTY_HOME)\zstd-1.4.0
|
||||
ZSTD_INCLUDE: $(ZSTD_HOME)\lib;$(ZSTD_HOME)\lib\dictBuilder
|
||||
ZSTD_LIB_DEBUG: $(ZSTD_HOME)\build\VS2010\bin\x64_Debug\libzstd_static.lib
|
||||
ZSTD_LIB_RELEASE: $(ZSTD_HOME)\build\VS2010\bin\x64_Release\libzstd_static.lib
|
||||
matrix:
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
|
||||
CMAKE_GENERATOR: Visual Studio 14 Win64
|
||||
DEV_ENV: C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.com
|
||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
CMAKE_GENERATOR: Visual Studio 15 Win64
|
||||
DEV_ENV: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\devenv.com
|
||||
|
||||
install:
|
||||
- md %THIRDPARTY_HOME%
|
||||
- echo "Building Snappy dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl --fail --silent --show-error --output snappy-1.1.7.zip --location https://github.com/google/snappy/archive/1.1.7.zip
|
||||
- unzip snappy-1.1.7.zip
|
||||
- cd snappy-1.1.7
|
||||
- mkdir build
|
||||
- cd build
|
||||
- if DEFINED CMAKE_PLATEFORM_NAME (set "PLATEFORM_OPT=-A %CMAKE_PLATEFORM_NAME%")
|
||||
- cmake .. -G "%CMAKE_GENERATOR%" %PLATEFORM_OPT%
|
||||
- msbuild Snappy.sln /p:Configuration=Debug /p:Platform=x64
|
||||
- msbuild Snappy.sln /p:Configuration=Release /p:Platform=x64
|
||||
- echo "Building LZ4 dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl --fail --silent --show-error --output lz4-1.8.3.zip --location https://github.com/lz4/lz4/archive/v1.8.3.zip
|
||||
- unzip lz4-1.8.3.zip
|
||||
- cd lz4-1.8.3\visual\VS2010
|
||||
- ps: $CMD="$Env:DEV_ENV"; & $CMD lz4.sln /upgrade
|
||||
- msbuild lz4.sln /p:Configuration=Debug /p:Platform=x64
|
||||
- msbuild lz4.sln /p:Configuration=Release /p:Platform=x64
|
||||
- echo "Building ZStd dependency..."
|
||||
- cd %THIRDPARTY_HOME%
|
||||
- curl --fail --silent --show-error --output zstd-1.4.0.zip --location https://github.com/facebook/zstd/archive/v1.4.0.zip
|
||||
- unzip zstd-1.4.0.zip
|
||||
- cd zstd-1.4.0\build\VS2010
|
||||
- ps: $CMD="$Env:DEV_ENV"; & $CMD zstd.sln /upgrade
|
||||
- msbuild zstd.sln /p:Configuration=Debug /p:Platform=x64
|
||||
- msbuild zstd.sln /p:Configuration=Release /p:Platform=x64
|
||||
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- if DEFINED CMAKE_PLATEFORM_NAME (set "PLATEFORM_OPT=-A %CMAKE_PLATEFORM_NAME%")
|
||||
- cmake .. -G "%CMAKE_GENERATOR%" %PLATEFORM_OPT% %CMAKE_OPT% -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DLZ4=1 -DZSTD=1 -DXPRESS=1 -DJNI=1 -DWITH_ALL_TESTS=0
|
||||
- cd ..
|
||||
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
parallel: true
|
||||
verbosity: normal
|
||||
|
||||
test:
|
||||
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,env_basic_test -Concurrency 8
|
||||
|
||||
on_failure:
|
||||
- cmd: 7z a build-failed.zip %APPVEYOR_BUILD_FOLDER%\build\ && appveyor PushArtifact build-failed.zip
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Executable → Regular
+15
-79
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
@@ -144,14 +143,7 @@ def generate_targets(repo_path, deps_map):
|
||||
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=[
|
||||
"//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"])
|
||||
src_mk["TOOL_LIB_SOURCES"])
|
||||
# rocksdb_whole_archive_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_whole_archive_lib",
|
||||
@@ -159,13 +151,7 @@ def generate_targets(repo_path, deps_map):
|
||||
# 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=[
|
||||
"//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"],
|
||||
deps=None,
|
||||
headers=None,
|
||||
extra_external_deps="",
|
||||
link_whole=True)
|
||||
@@ -177,8 +163,9 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk.get("EXP_LIB_SOURCES", []) +
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"],
|
||||
extra_test_libs=True
|
||||
)
|
||||
extra_external_deps=""" + [
|
||||
("googletest", None, "gtest"),
|
||||
]""")
|
||||
# rocksdb_tools_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_tools_lib",
|
||||
@@ -197,23 +184,12 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", [])
|
||||
+ src_mk.get('STRESS_LIB_SOURCES', [])
|
||||
+ ["test_util/testutil.cc"])
|
||||
# db_stress binary
|
||||
TARGETS.add_binary("db_stress",
|
||||
["db_stress_tool/db_stress.cc"],
|
||||
[":rocksdb_stress_lib"])
|
||||
# bench binaries
|
||||
for src in src_mk.get("MICROBENCH_SOURCES", []):
|
||||
name = src.rsplit('/',1)[1].split('.')[0] if '/' in src else src.split('.')[0]
|
||||
TARGETS.add_binary(
|
||||
name,
|
||||
[src],
|
||||
[],
|
||||
extra_bench_libs=True
|
||||
)
|
||||
|
||||
print("Extra dependencies:\n{0}".format(json.dumps(deps_map)))
|
||||
|
||||
# Dictionary test executable name -> relative source file path
|
||||
test_source_map = {}
|
||||
print(src_mk)
|
||||
|
||||
# c_test.c is added through TARGETS.add_c_test(). If there
|
||||
# are more than one .c test file, we need to extend
|
||||
@@ -224,42 +200,6 @@ def generate_targets(repo_path, deps_map):
|
||||
return False
|
||||
TARGETS.add_c_test()
|
||||
|
||||
try:
|
||||
with open(f"{repo_path}/buckifier/bench.json") as json_file:
|
||||
fast_fancy_bench_config_list = json.load(json_file)
|
||||
for config_dict in fast_fancy_bench_config_list:
|
||||
clean_benchmarks = {}
|
||||
benchmarks = config_dict['benchmarks']
|
||||
for binary, benchmark_dict in benchmarks.items():
|
||||
clean_benchmarks[binary] = {}
|
||||
for benchmark, overloaded_metric_list in benchmark_dict.items():
|
||||
clean_benchmarks[binary][benchmark] = []
|
||||
for metric in overloaded_metric_list:
|
||||
if not isinstance(metric, dict):
|
||||
clean_benchmarks[binary][benchmark].append(metric)
|
||||
TARGETS.add_fancy_bench_config(config_dict['name'], clean_benchmarks, False, config_dict['expected_runtime_one_iter'], config_dict['sl_iterations'], config_dict['regression_threshold'])
|
||||
|
||||
with open(f"{repo_path}/buckifier/bench-slow.json") as json_file:
|
||||
slow_fancy_bench_config_list = json.load(json_file)
|
||||
for config_dict in slow_fancy_bench_config_list:
|
||||
clean_benchmarks = {}
|
||||
benchmarks = config_dict['benchmarks']
|
||||
for binary, benchmark_dict in benchmarks.items():
|
||||
clean_benchmarks[binary] = {}
|
||||
for benchmark, overloaded_metric_list in benchmark_dict.items():
|
||||
clean_benchmarks[binary][benchmark] = []
|
||||
for metric in overloaded_metric_list:
|
||||
if not isinstance(metric, dict):
|
||||
clean_benchmarks[binary][benchmark].append(metric)
|
||||
for config_dict in slow_fancy_bench_config_list:
|
||||
TARGETS.add_fancy_bench_config(config_dict['name']+"_slow", clean_benchmarks, True, config_dict['expected_runtime_one_iter'], config_dict['sl_iterations'], config_dict['regression_threshold'])
|
||||
# it is better servicelab experiments break
|
||||
# than rocksdb github ci
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
TARGETS.add_test_header()
|
||||
|
||||
for test_src in src_mk.get("TEST_MAIN_SOURCES", []):
|
||||
test = test_src.split('.c')[0].strip().split('/')[-1].strip()
|
||||
test_source_map[test] = test_src
|
||||
@@ -273,21 +213,17 @@ def generate_targets(repo_path, deps_map):
|
||||
|
||||
test_target_name = \
|
||||
test if not target_alias else test + "_" + target_alias
|
||||
TARGETS.register_test(
|
||||
test_target_name,
|
||||
test_src,
|
||||
test not in non_parallel_tests,
|
||||
json.dumps(deps['extra_deps']),
|
||||
json.dumps(deps['extra_compiler_flags']))
|
||||
|
||||
if test in _EXPORTED_TEST_LIBS:
|
||||
test_library = "%s_lib" % test_target_name
|
||||
TARGETS.add_library(test_library, [test_src], deps=[":rocksdb_test_lib"], extra_test_libs=True)
|
||||
TARGETS.register_test(
|
||||
test_target_name,
|
||||
test_src,
|
||||
deps = json.dumps(deps['extra_deps'] + [':'+test_library]),
|
||||
extra_compiler_flags = json.dumps(deps['extra_compiler_flags']))
|
||||
else:
|
||||
TARGETS.register_test(
|
||||
test_target_name,
|
||||
test_src,
|
||||
deps = json.dumps(deps['extra_deps'] + [":rocksdb_test_lib"] ),
|
||||
extra_compiler_flags = json.dumps(deps['extra_compiler_flags']))
|
||||
TARGETS.add_library(test_library, [test_src], [":rocksdb_test_lib"])
|
||||
TARGETS.flush_tests()
|
||||
|
||||
print(ColorString.info("Generated TARGETS Summary:"))
|
||||
print(ColorString.info("- %d libs" % TARGETS.total_lib))
|
||||
|
||||
@@ -10,7 +10,6 @@ except ImportError:
|
||||
from __builtin__ import object
|
||||
from __builtin__ import str
|
||||
import targets_cfg
|
||||
import pprint
|
||||
|
||||
def pretty_list(lst, indent=8):
|
||||
if lst is None or len(lst) == 0:
|
||||
@@ -41,73 +40,85 @@ class TARGETSBuilder(object):
|
||||
self.targets_file.close()
|
||||
|
||||
def add_library(self, name, srcs, deps=None, headers=None,
|
||||
extra_external_deps="", link_whole=False,
|
||||
external_dependencies=None, extra_test_libs=False):
|
||||
if headers is not None:
|
||||
extra_external_deps="", link_whole=False):
|
||||
headers_attr_prefix = ""
|
||||
if headers is None:
|
||||
headers_attr_prefix = "auto_"
|
||||
headers = "AutoHeaders.RECURSIVE_GLOB"
|
||||
else:
|
||||
headers = "[" + pretty_list(headers) + "]"
|
||||
self.targets_file.write(targets_cfg.library_template.format(
|
||||
name=name,
|
||||
srcs=pretty_list(srcs),
|
||||
headers_attr_prefix=headers_attr_prefix,
|
||||
headers=headers,
|
||||
deps=pretty_list(deps),
|
||||
extra_external_deps=extra_external_deps,
|
||||
link_whole=link_whole,
|
||||
external_dependencies=pretty_list(external_dependencies),
|
||||
extra_test_libs=extra_test_libs
|
||||
).encode("utf-8"))
|
||||
link_whole=link_whole).encode("utf-8"))
|
||||
self.total_lib = self.total_lib + 1
|
||||
|
||||
def add_rocksdb_library(self, name, srcs, headers=None,
|
||||
external_dependencies=None):
|
||||
if headers is not None:
|
||||
def add_rocksdb_library(self, name, srcs, headers=None):
|
||||
headers_attr_prefix = ""
|
||||
if headers is None:
|
||||
headers_attr_prefix = "auto_"
|
||||
headers = "AutoHeaders.RECURSIVE_GLOB"
|
||||
else:
|
||||
headers = "[" + pretty_list(headers) + "]"
|
||||
self.targets_file.write(targets_cfg.rocksdb_library_template.format(
|
||||
name=name,
|
||||
srcs=pretty_list(srcs),
|
||||
headers=headers,
|
||||
external_dependencies=pretty_list(external_dependencies)
|
||||
).encode("utf-8")
|
||||
)
|
||||
headers_attr_prefix=headers_attr_prefix,
|
||||
headers=headers).encode("utf-8"))
|
||||
self.total_lib = self.total_lib + 1
|
||||
|
||||
def add_binary(self, name, srcs, deps=None, extra_preprocessor_flags=None,extra_bench_libs=False):
|
||||
def add_binary(self, name, srcs, deps=None):
|
||||
self.targets_file.write(targets_cfg.binary_template.format(
|
||||
name=name,
|
||||
srcs=pretty_list(srcs),
|
||||
deps=pretty_list(deps),
|
||||
extra_preprocessor_flags=pretty_list(extra_preprocessor_flags),
|
||||
extra_bench_libs=extra_bench_libs,
|
||||
).encode("utf-8"))
|
||||
deps=pretty_list(deps)).encode("utf-8"))
|
||||
self.total_bin = self.total_bin + 1
|
||||
|
||||
def add_c_test(self):
|
||||
self.targets_file.write(b"""
|
||||
add_c_test_wrapper()
|
||||
""")
|
||||
cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
) if not is_opt_mode else None
|
||||
|
||||
def add_test_header(self):
|
||||
self.targets_file.write(b"""
|
||||
# Generate a test rule for each entry in ROCKS_TESTS
|
||||
# Do not build the tests in opt mode, since SyncPoint and other test code
|
||||
# will not be included.
|
||||
custom_unittest(
|
||||
name = "c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
) if not is_opt_mode else None
|
||||
""")
|
||||
|
||||
def add_fancy_bench_config(self, name, bench_config, slow, expected_runtime, sl_iterations, regression_threshold):
|
||||
self.targets_file.write(targets_cfg.fancy_bench_template.format(
|
||||
name=name,
|
||||
bench_config=pprint.pformat(bench_config),
|
||||
slow=slow,
|
||||
expected_runtime=expected_runtime,
|
||||
sl_iterations=sl_iterations,
|
||||
regression_threshold=regression_threshold
|
||||
).encode("utf-8"))
|
||||
|
||||
def register_test(self,
|
||||
test_name,
|
||||
src,
|
||||
deps,
|
||||
is_parallel,
|
||||
extra_deps,
|
||||
extra_compiler_flags):
|
||||
exec_mode = "serial"
|
||||
if is_parallel:
|
||||
exec_mode = "parallel"
|
||||
self.tests_cfg += targets_cfg.test_cfg_template % (
|
||||
test_name,
|
||||
str(src),
|
||||
str(exec_mode),
|
||||
extra_deps,
|
||||
extra_compiler_flags)
|
||||
|
||||
self.targets_file.write(targets_cfg.unittests_template.format(test_name=test_name,test_cc=str(src),deps=deps,
|
||||
extra_compiler_flags=extra_compiler_flags).encode("utf-8"))
|
||||
self.total_test = self.total_test + 1
|
||||
|
||||
def flush_tests(self):
|
||||
self.targets_file.write(targets_cfg.unittests_template.format(tests=self.tests_cfg).encode("utf-8"))
|
||||
self.tests_cfg = ""
|
||||
+191
-17
@@ -11,36 +11,210 @@ rocksdb_target_header_template = \
|
||||
# This file is a Facebook-specific integration for buck builds, so can
|
||||
# only be validated by Facebook employees.
|
||||
#
|
||||
# @noautodeps @nocodemods
|
||||
load("//rocks/buckifier:defs.bzl", "cpp_library_wrapper","rocks_cpp_library_wrapper","cpp_binary_wrapper","cpp_unittest_wrapper","fancy_bench_wrapper","add_c_test_wrapper")
|
||||
load("@fbcode_macros//build_defs:auto_headers.bzl", "AutoHeaders")
|
||||
load("@fbcode_macros//build_defs:cpp_library.bzl", "cpp_library")
|
||||
load(":defs.bzl", "test_binary")
|
||||
|
||||
REPO_PATH = package_name() + "/"
|
||||
|
||||
ROCKSDB_COMPILER_FLAGS_0 = [
|
||||
"-fno-builtin-memcmp",
|
||||
# Needed to compile in fbcode
|
||||
"-Wno-expansion-to-defined",
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-Wnarrowing",
|
||||
"-DROCKSDB_NO_DYNAMIC_EXTENSION",
|
||||
]
|
||||
|
||||
ROCKSDB_EXTERNAL_DEPS = [
|
||||
("bzip2", None, "bz2"),
|
||||
("snappy", None, "snappy"),
|
||||
("zlib", None, "z"),
|
||||
("gflags", None, "gflags"),
|
||||
("lz4", None, "lz4"),
|
||||
("zstd", None, "zstd"),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_DEPS_0 = [
|
||||
(
|
||||
"linux",
|
||||
["third-party//numa:numa", "third-party//liburing:uring", "third-party//tbb:tbb"],
|
||||
),
|
||||
(
|
||||
"macos",
|
||||
["third-party//tbb:tbb"],
|
||||
),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS_0 = [
|
||||
(
|
||||
"linux",
|
||||
[
|
||||
"-DOS_LINUX",
|
||||
"-DROCKSDB_FALLOCATE_PRESENT",
|
||||
"-DROCKSDB_MALLOC_USABLE_SIZE",
|
||||
"-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX",
|
||||
"-DROCKSDB_RANGESYNC_PRESENT",
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DROCKSDB_IOURING_PRESENT",
|
||||
"-DHAVE_SSE42",
|
||||
"-DLIBURING",
|
||||
"-DNUMA",
|
||||
"-DROCKSDB_PLATFORM_POSIX",
|
||||
"-DROCKSDB_LIB_IO_POSIX",
|
||||
"-DTBB",
|
||||
],
|
||||
),
|
||||
(
|
||||
"macos",
|
||||
[
|
||||
"-DOS_MACOSX",
|
||||
"-DROCKSDB_PLATFORM_POSIX",
|
||||
"-DROCKSDB_LIB_IO_POSIX",
|
||||
"-DTBB",
|
||||
],
|
||||
),
|
||||
(
|
||||
"windows",
|
||||
[ "-DOS_WIN", "-DWIN32", "-D_MBCS", "-DWIN64", "-DNOMINMAX" ]
|
||||
),
|
||||
]
|
||||
|
||||
ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
"-DROCKSDB_SUPPORT_THREAD_LOCAL",
|
||||
|
||||
# Flags to enable libs we include
|
||||
"-DSNAPPY",
|
||||
"-DZLIB",
|
||||
"-DBZIP2",
|
||||
"-DLZ4",
|
||||
"-DZSTD",
|
||||
"-DZSTD_STATIC_LINKING_ONLY",
|
||||
"-DGFLAGS=gflags",
|
||||
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
]
|
||||
|
||||
# Directories with files for #include
|
||||
ROCKSDB_INCLUDE_PATHS = [
|
||||
"",
|
||||
"include",
|
||||
]
|
||||
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {{
|
||||
"x86_64": [
|
||||
"-DHAVE_PCLMUL",
|
||||
],
|
||||
}}
|
||||
|
||||
build_mode = read_config("fbcode", "build_mode")
|
||||
|
||||
is_opt_mode = build_mode.startswith("opt")
|
||||
|
||||
# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice
|
||||
# doesn't harm and avoid forgetting to add it.
|
||||
ROCKSDB_COMPILER_FLAGS = ROCKSDB_COMPILER_FLAGS_0 + (["-DNDEBUG"] if is_opt_mode else [])
|
||||
|
||||
sanitizer = read_config("fbcode", "sanitizer")
|
||||
|
||||
# Do not enable jemalloc if sanitizer presents. RocksDB will further detect
|
||||
# whether the binary is linked with jemalloc at runtime.
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS = ROCKSDB_OS_PREPROCESSOR_FLAGS_0 + ([(
|
||||
"linux",
|
||||
["-DROCKSDB_JEMALLOC"],
|
||||
)] if sanitizer == "" else [])
|
||||
|
||||
ROCKSDB_OS_DEPS = ROCKSDB_OS_DEPS_0 + ([(
|
||||
"linux",
|
||||
["third-party//jemalloc:headers"],
|
||||
)] if sanitizer == "" else [])
|
||||
|
||||
ROCKSDB_LIB_DEPS = [
|
||||
":rocksdb_lib",
|
||||
":rocksdb_test_lib",
|
||||
] if not is_opt_mode else [":rocksdb_lib"]
|
||||
"""
|
||||
|
||||
|
||||
library_template = """
|
||||
cpp_library_wrapper(name="{name}", srcs=[{srcs}], deps=[{deps}], headers={headers}, link_whole={link_whole}, extra_test_libs={extra_test_libs})
|
||||
cpp_library(
|
||||
name = "{name}",
|
||||
srcs = [{srcs}],
|
||||
{headers_attr_prefix}headers = {headers},
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS{extra_external_deps},
|
||||
link_whole = {link_whole},
|
||||
)
|
||||
"""
|
||||
|
||||
rocksdb_library_template = """
|
||||
rocks_cpp_library_wrapper(name="{name}", srcs=[{srcs}], headers={headers})
|
||||
|
||||
cpp_library(
|
||||
name = "{name}",
|
||||
srcs = [{srcs}],
|
||||
{headers_attr_prefix}headers = {headers},
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = ROCKSDB_LIB_DEPS,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
"""
|
||||
|
||||
|
||||
|
||||
binary_template = """
|
||||
cpp_binary_wrapper(name="{name}", srcs=[{srcs}], deps=[{deps}], extra_preprocessor_flags=[{extra_preprocessor_flags}], extra_bench_libs={extra_bench_libs})
|
||||
cpp_binary(
|
||||
name = "{name}",
|
||||
srcs = [{srcs}],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
"""
|
||||
|
||||
test_cfg_template = """ [
|
||||
"%s",
|
||||
"%s",
|
||||
"%s",
|
||||
%s,
|
||||
%s,
|
||||
],
|
||||
"""
|
||||
|
||||
unittests_template = """
|
||||
cpp_unittest_wrapper(name="{test_name}",
|
||||
srcs=["{test_cc}"],
|
||||
deps={deps},
|
||||
extra_compiler_flags={extra_compiler_flags})
|
||||
|
||||
"""
|
||||
|
||||
fancy_bench_template = """
|
||||
fancy_bench_wrapper(suite_name="{name}", binary_to_bench_to_metric_list_map={bench_config}, slow={slow}, expected_runtime={expected_runtime}, sl_iterations={sl_iterations}, regression_threshold={regression_threshold})
|
||||
# [test_name, test_src, test_type, extra_deps, extra_compiler_flags]
|
||||
ROCKS_TESTS = [
|
||||
{tests}]
|
||||
|
||||
# Generate a test rule for each entry in ROCKS_TESTS
|
||||
# Do not build the tests in opt mode, since SyncPoint and other test code
|
||||
# will not be included.
|
||||
[
|
||||
cpp_unittest(
|
||||
name = test_name,
|
||||
srcs = [test_cc],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS + extra_compiler_flags,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [":rocksdb_test_lib"] + extra_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
],
|
||||
)
|
||||
for test_name, test_cc, parallelism, extra_deps, extra_compiler_flags in ROCKS_TESTS
|
||||
if not is_opt_mode
|
||||
]
|
||||
"""
|
||||
@@ -1,187 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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).
|
||||
|
||||
'''Access the results of benchmark runs
|
||||
Send these results on to OpenSearch graphing service
|
||||
'''
|
||||
|
||||
import argparse
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import requests
|
||||
from dateutil import parser
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
class Configuration:
|
||||
opensearch_user = os.environ['ES_USER']
|
||||
opensearch_pass = os.environ['ES_PASS']
|
||||
|
||||
class BenchmarkResultException(Exception):
|
||||
def __init__(self, message, content):
|
||||
super().__init__(self, message)
|
||||
self.content = content
|
||||
|
||||
|
||||
class BenchmarkUtils:
|
||||
|
||||
expected_keys = ['ops_sec', 'mb_sec', 'lsm_sz', 'blob_sz', 'c_wgb', 'w_amp',
|
||||
'c_mbps', 'c_wsecs', 'c_csecs', 'b_rgb', 'b_wgb', 'usec_op',
|
||||
'p50', 'p99', 'p99.9', 'p99.99', 'pmax',
|
||||
'uptime', 'stall%', 'Nstall', 'u_cpu', 's_cpu', 'rss', 'test', 'date', 'version', 'job_id']
|
||||
|
||||
def sanity_check(row):
|
||||
if not 'test' in row:
|
||||
logging.debug(f"not 'test' in row: {row}")
|
||||
return False
|
||||
if row['test'] == '':
|
||||
logging.debug(f"row['test'] == '': {row}")
|
||||
return False
|
||||
if not 'date' in row:
|
||||
logging.debug(f"not 'date' in row: {row}")
|
||||
return False
|
||||
if not 'ops_sec' in row:
|
||||
logging.debug(f"not 'ops_sec' in row: {row}")
|
||||
return False
|
||||
try:
|
||||
v = int(row['ops_sec'])
|
||||
except (ValueError, TypeError):
|
||||
logging.debug(f"int(row['ops_sec']): {row}")
|
||||
return False
|
||||
try:
|
||||
(_, _) = parser.parse(row['date'], fuzzy_with_tokens=True)
|
||||
except (parser.ParserError):
|
||||
logging.error(f"parser.parse((row['date']): not a valid format for date in row: {row}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def conform_opensearch(row):
|
||||
(dt, _) = parser.parse(row['date'], fuzzy_with_tokens=True)
|
||||
# create a test_date field, which was previously what was expected
|
||||
# repair the date field, which has what can be a WRONG ISO FORMAT, (no leading 0 on single-digit day-of-month)
|
||||
# e.g. 2022-07-1T00:14:55 should be 2022-07-01T00:14:55
|
||||
row['test_date'] = dt.isoformat()
|
||||
row['date'] = dt.isoformat()
|
||||
return dict((key.replace('.', '_'), value)
|
||||
for (key, value) in row.items())
|
||||
|
||||
|
||||
class ResultParser:
|
||||
def __init__(self, field="(\w|[+-:.%])+", intrafield="(\s)+", separator="\t"):
|
||||
self.field = re.compile(field)
|
||||
self.intra = re.compile(intrafield)
|
||||
self.sep = re.compile(separator)
|
||||
|
||||
def ignore(self, l_in: str):
|
||||
if len(l_in) == 0:
|
||||
return True
|
||||
if l_in[0:1] == '#':
|
||||
return True
|
||||
return False
|
||||
|
||||
def line(self, l_in: str):
|
||||
'''Parse a line into items
|
||||
Being clever about separators
|
||||
'''
|
||||
l = l_in
|
||||
row = []
|
||||
while l != '':
|
||||
match_item = self.field.match(l)
|
||||
if match_item:
|
||||
item = match_item.group(0)
|
||||
row.append(item)
|
||||
l = l[len(item):]
|
||||
else:
|
||||
match_intra = self.intra.match(l)
|
||||
if match_intra:
|
||||
intra = match_intra.group(0)
|
||||
# Count the separators
|
||||
# If there are >1 then generate extra blank fields
|
||||
# White space with no true separators fakes up a single separator
|
||||
tabbed = self.sep.split(intra)
|
||||
sep_count = len(tabbed) - 1
|
||||
if sep_count == 0:
|
||||
sep_count = 1
|
||||
for i in range(sep_count-1):
|
||||
row.append('')
|
||||
l = l[len(intra):]
|
||||
else:
|
||||
raise BenchmarkResultException(
|
||||
'Invalid TSV line', f"{l_in} at {l}")
|
||||
return row
|
||||
|
||||
def parse(self, lines):
|
||||
'''Parse something that iterates lines'''
|
||||
rows = [self.line(line) for line in lines if not self.ignore(line)]
|
||||
header = rows[0]
|
||||
width = len(header)
|
||||
records = [{k: v for (k, v) in itertools.zip_longest(
|
||||
header, row[:width])} for row in rows[1:]]
|
||||
return records
|
||||
|
||||
|
||||
def load_report_from_tsv(filename: str):
|
||||
file = open(filename, 'r')
|
||||
contents = file.readlines()
|
||||
file.close()
|
||||
parser = ResultParser()
|
||||
report = parser.parse(contents)
|
||||
logging.debug(f"Loaded TSV Report: {report}")
|
||||
return report
|
||||
|
||||
|
||||
def push_report_to_opensearch(report, esdocument):
|
||||
sanitized = [BenchmarkUtils.conform_opensearch(row)
|
||||
for row in report if BenchmarkUtils.sanity_check(row)]
|
||||
logging.debug(f"upload {len(sanitized)} sane of {len(report)} benchmarks to opensearch")
|
||||
for single_benchmark in sanitized:
|
||||
logging.debug(f"upload benchmark: {single_benchmark}")
|
||||
response = requests.post(
|
||||
esdocument,
|
||||
json=single_benchmark, auth=(os.environ['ES_USER'], os.environ['ES_PASS']))
|
||||
logging.debug(
|
||||
f"Sent to OpenSearch, status: {response.status_code}, result: {response.text}")
|
||||
response.raise_for_status()
|
||||
|
||||
def push_report_to_null(report):
|
||||
|
||||
for row in report:
|
||||
if BenchmarkUtils.sanity_check(row):
|
||||
logging.debug(f"row {row}")
|
||||
conformed = BenchmarkUtils.conform_opensearch(row)
|
||||
logging.debug(f"conformed row {conformed}")
|
||||
|
||||
def main():
|
||||
'''Tool for fetching, parsing and uploading benchmark results to OpenSearch / ElasticSearch
|
||||
This tool will
|
||||
|
||||
(1) Open a local tsv benchmark report file
|
||||
(2) Upload to OpenSearch document, via https/JSON
|
||||
'''
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='CircleCI benchmark scraper.')
|
||||
|
||||
# --tsvfile is the name of the file to read results from
|
||||
# --esdocument is the ElasticSearch document to push these results into
|
||||
#
|
||||
parser.add_argument('--tsvfile', default='build_tools/circle_api_scraper_input.txt',
|
||||
help='File from which to read tsv report')
|
||||
parser.add_argument('--esdocument', help='ElasticSearch/OpenSearch document URL to upload report into')
|
||||
parser.add_argument('--upload', choices=['opensearch', 'none'], default='opensearch')
|
||||
|
||||
args = parser.parse_args()
|
||||
logging.debug(f"Arguments: {args}")
|
||||
reports = load_report_from_tsv(args.tsvfile)
|
||||
if (args.upload == 'opensearch'):
|
||||
push_report_to_opensearch(reports, args.esdocument)
|
||||
else:
|
||||
push_report_to_null(reports)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -45,11 +45,11 @@ if test -z "$OUTPUT"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# we depend on C++17, but should be compatible with newer standards
|
||||
# we depend on C++11, but should be compatible with newer standards
|
||||
if [ "$ROCKSDB_CXX_STANDARD" ]; then
|
||||
PLATFORM_CXXFLAGS="-std=$ROCKSDB_CXX_STANDARD"
|
||||
else
|
||||
PLATFORM_CXXFLAGS="-std=c++17"
|
||||
PLATFORM_CXXFLAGS="-std=c++11"
|
||||
fi
|
||||
|
||||
# we currently depend on POSIX platform
|
||||
@@ -58,13 +58,15 @@ COMMON_FLAGS="-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX"
|
||||
# Default to fbcode gcc on internal fb machines
|
||||
if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
FBCODE_BUILD="true"
|
||||
# If we're compiling with TSAN or shared lib, we need pic build
|
||||
# If we're compiling with TSAN we need pic build
|
||||
PIC_BUILD=$COMPILE_WITH_TSAN
|
||||
if [ "$LIB_MODE" == "shared" ]; then
|
||||
PIC_BUILD=1
|
||||
fi
|
||||
if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM010" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform010.sh"
|
||||
if [ -n "$ROCKSDB_FBCODE_BUILD_WITH_481" ]; then
|
||||
# we need this to build with MySQL. Don't use for other purposes.
|
||||
source "$PWD/build_tools/fbcode_config4.8.1.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_5xx" ]; then
|
||||
source "$PWD/build_tools/fbcode_config.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM009" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform009.sh"
|
||||
else
|
||||
@@ -174,7 +176,7 @@ case "$TARGET_OS" in
|
||||
fi
|
||||
if test "$ROCKSDB_USE_IO_URING" -ne 0; then
|
||||
# check for liburing
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -luring -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -luring -o /dev/null 2>/dev/null <<EOF
|
||||
#include <liburing.h>
|
||||
int main() {
|
||||
struct io_uring ring;
|
||||
@@ -269,7 +271,7 @@ esac
|
||||
PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS ${CXXFLAGS}"
|
||||
JAVA_LDFLAGS="$PLATFORM_LDFLAGS"
|
||||
JAVA_STATIC_LDFLAGS="$PLATFORM_LDFLAGS"
|
||||
JAVAC_ARGS="-source 8"
|
||||
JAVAC_ARGS="-source 7"
|
||||
|
||||
if [ "$CROSS_COMPILE" = "true" -o "$FBCODE_BUILD" = "true" ]; then
|
||||
# Cross-compiling; do not try any compilation tests.
|
||||
@@ -277,13 +279,12 @@ if [ "$CROSS_COMPILE" = "true" -o "$FBCODE_BUILD" = "true" ]; then
|
||||
if [ "$FBCODE_BUILD" = "true" ]; then
|
||||
# Enable backtrace on fbcode since the necessary libraries are present
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
FOLLY_DIR="third-party/folly"
|
||||
fi
|
||||
true
|
||||
else
|
||||
if ! test $ROCKSDB_DISABLE_FALLOCATE; then
|
||||
# Test whether fallocate is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
#include <linux/falloc.h>
|
||||
int main() {
|
||||
@@ -299,7 +300,7 @@ EOF
|
||||
if ! test $ROCKSDB_DISABLE_SNAPPY; then
|
||||
# Test whether Snappy library is installed
|
||||
# http://code.google.com/p/snappy/
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <snappy.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -314,7 +315,7 @@ EOF
|
||||
# Test whether gflags library is installed
|
||||
# http://gflags.github.io/gflags/
|
||||
# check if the namespace is gflags
|
||||
if $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
|
||||
if $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace GFLAGS_NAMESPACE;
|
||||
int main() {}
|
||||
@@ -323,7 +324,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# check if namespace is gflags
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace gflags;
|
||||
int main() {}
|
||||
@@ -332,7 +333,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# check if namespace is google
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null << EOF
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace google;
|
||||
int main() {}
|
||||
@@ -345,7 +346,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZLIB; then
|
||||
# Test whether zlib library is installed
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zlib.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -358,7 +359,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BZIP; then
|
||||
# Test whether bzip library is installed
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <bzlib.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -371,7 +372,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_LZ4; then
|
||||
# Test whether lz4 library is installed
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <lz4.h>
|
||||
#include <lz4hc.h>
|
||||
int main() {}
|
||||
@@ -398,7 +399,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_NUMA; then
|
||||
# Test whether numa is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o -lnuma 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
|
||||
#include <numa.h>
|
||||
#include <numaif.h>
|
||||
int main() {}
|
||||
@@ -412,7 +413,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_TBB; then
|
||||
# Test whether tbb is available
|
||||
$CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o test.o -ltbb 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null <<EOF
|
||||
#include <tbb/tbb.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -425,7 +426,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_JEMALLOC; then
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o -ljemalloc \
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
2>/dev/null; then
|
||||
# This will enable some preprocessor identifiers in the Makefile
|
||||
JEMALLOC=1
|
||||
@@ -446,7 +447,7 @@ EOF
|
||||
fi
|
||||
if ! test $JEMALLOC && ! test $ROCKSDB_DISABLE_TCMALLOC; then
|
||||
# jemalloc is not available. Let's try tcmalloc
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o \
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null \
|
||||
-ltcmalloc 2>/dev/null; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltcmalloc"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltcmalloc"
|
||||
@@ -455,7 +456,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_MALLOC_USABLE_SIZE; then
|
||||
# Test whether malloc_usable_size is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <malloc.h>
|
||||
int main() {
|
||||
size_t res = malloc_usable_size(0);
|
||||
@@ -470,7 +471,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_MEMKIND; then
|
||||
# Test whether memkind library is installed
|
||||
$CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o test.o -lmemkind 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -lmemkind -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <memkind.h>
|
||||
int main() {
|
||||
memkind_malloc(MEMKIND_DAX_KMEM, 1024);
|
||||
@@ -486,7 +487,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_PTHREAD_MUTEX_ADAPTIVE_NP; then
|
||||
# Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <pthread.h>
|
||||
int main() {
|
||||
int x = PTHREAD_MUTEX_ADAPTIVE_NP;
|
||||
@@ -501,7 +502,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BACKTRACE; then
|
||||
# Test whether backtrace is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
@@ -513,7 +514,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
else
|
||||
# Test whether execinfo library is installed
|
||||
$CXX $PLATFORM_CXXFLAGS -lexecinfo -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
@@ -530,7 +531,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_PG; then
|
||||
# Test if -pg is supported
|
||||
$CXX $PLATFORM_CXXFLAGS -pg -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
@@ -542,7 +543,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SYNC_FILE_RANGE; then
|
||||
# Test whether sync_file_range is supported for compatibility with an old glibc
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
int main() {
|
||||
int fd = open("/dev/null", 0);
|
||||
@@ -556,7 +557,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SCHED_GETCPU; then
|
||||
# Test whether sched_getcpu is supported
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sched.h>
|
||||
int main() {
|
||||
int cpuid = sched_getcpu();
|
||||
@@ -570,7 +571,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_AUXV_GETAUXVAL; then
|
||||
# Test whether getauxval is supported
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sys/auxv.h>
|
||||
int main() {
|
||||
uint64_t auxv = getauxval(AT_HWCAP);
|
||||
@@ -584,7 +585,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ALIGNED_NEW; then
|
||||
# Test whether c++17 aligned-new is supported
|
||||
$CXX $PLATFORM_CXXFLAGS -faligned-new -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -faligned-new -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
struct alignas(1024) t {int a;};
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -594,7 +595,7 @@ EOF
|
||||
fi
|
||||
if ! test $ROCKSDB_DISABLE_BENCHMARK; then
|
||||
# Test whether google benchmark is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -lbenchmark -lpthread 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -lbenchmark 2>/dev/null <<EOF
|
||||
#include <benchmark/benchmark.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -602,24 +603,13 @@ EOF
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbenchmark"
|
||||
fi
|
||||
fi
|
||||
if test $USE_FOLLY; then
|
||||
# Test whether libfolly library is installed
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <folly/synchronization/DistributedMutex.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" != 0 ]; then
|
||||
FOLLY_DIR="./third-party/folly"
|
||||
fi
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# TODO(tec): Fix -Wshorten-64-to-32 errors on FreeBSD and enable the warning.
|
||||
# -Wshorten-64-to-32 breaks compilation on FreeBSD aarch64 and i386
|
||||
if ! { [ "$TARGET_OS" = FreeBSD ] && [ "$TARGET_ARCHITECTURE" = arm64 -o "$TARGET_ARCHITECTURE" = i386 ]; }; then
|
||||
# Test whether -Wshorten-64-to-32 is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o test.o -Wshorten-64-to-32 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -Wshorten-64-to-32 2>/dev/null <<EOF
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
@@ -627,27 +617,34 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# shall we use HDFS?
|
||||
|
||||
if test "$USE_HDFS"; then
|
||||
if test -z "$JAVA_HOME"; then
|
||||
echo "JAVA_HOME has to be set for HDFS usage." >&2
|
||||
exit 1
|
||||
fi
|
||||
HDFS_CCFLAGS="$HDFS_CCFLAGS -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -DUSE_HDFS -I$HADOOP_HOME/include"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -lhdfs -L$JAVA_HOME/jre/lib/amd64 -L$HADOOP_HOME/lib/native"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -L$JAVA_HOME/jre/lib/amd64/server -L$GLIBC_RUNTIME_PATH/lib"
|
||||
HDFS_LDFLAGS="$HDFS_LDFLAGS -ldl -lverify -ljava -ljvm"
|
||||
COMMON_FLAGS="$COMMON_FLAGS $HDFS_CCFLAGS"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS $HDFS_LDFLAGS"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS $HDFS_LDFLAGS"
|
||||
fi
|
||||
|
||||
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]+`
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mcpu=$POWER -mtune=$POWER "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z10 "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep -e^arm -e^aarch64`"; then
|
||||
# TODO: Handle this with approprite options.
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^aarch64`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ \
|
||||
-march=native - -o /dev/null 2>/dev/null; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=native "
|
||||
else
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z196 "
|
||||
fi
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
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 [ "$TARGET_OS" == "IOS" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif [ "$TARGET_OS" == "AIX" ] || [ "$TARGET_OS" == "SunOS" ]; then
|
||||
@@ -664,23 +661,13 @@ else
|
||||
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.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.13 $PLATFORM_SHARED_LDFLAGS"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.13"
|
||||
# For portability compile for macOS 10.12 (2016) or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.12"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.12"
|
||||
PLATFORM_SHARED_LDFLAGS="$PLATFORM_SHARED_LDFLAGS -mmacosx-version-min=10.12"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.12"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.12"
|
||||
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"
|
||||
@@ -725,7 +712,7 @@ if test "$TRY_SSE_ETC"; then
|
||||
TRY_LZCNT="-mlzcnt"
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_SSE42 -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_SSE42 -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <nmmintrin.h>
|
||||
int main() {
|
||||
@@ -739,7 +726,7 @@ 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
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_PCLMUL -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <wmmintrin.h>
|
||||
int main() {
|
||||
@@ -756,7 +743,7 @@ 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
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_AVX2 -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main() {
|
||||
@@ -771,7 +758,7 @@ 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
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_BMI -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main(int argc, char *argv[]) {
|
||||
@@ -785,7 +772,7 @@ 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
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS $TRY_LZCNT -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <immintrin.h>
|
||||
int main(int argc, char *argv[]) {
|
||||
@@ -799,7 +786,7 @@ 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
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
int main() {
|
||||
uint64_t a = 0xffffFFFFffffFFFF;
|
||||
@@ -812,12 +799,31 @@ if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DHAVE_UINT128_EXTENSION"
|
||||
fi
|
||||
|
||||
# iOS doesn't support thread-local storage, but this check would erroneously
|
||||
# succeed because the cross-compiler flags are added by the Makefile, not this
|
||||
# script.
|
||||
if [ "$PLATFORM" != IOS ]; then
|
||||
$CXX $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#if defined(_MSC_VER) && !defined(__thread)
|
||||
#define __thread __declspec(thread)
|
||||
#endif
|
||||
int main() {
|
||||
static __thread int tls;
|
||||
(void)tls;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_SUPPORT_THREAD_LOCAL"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
if [ "$FBCODE_BUILD" != "true" -a "$PLATFORM" = OS_LINUX ]; then
|
||||
$CXX $COMMON_FLAGS $PLATFORM_SHARED_CFLAGS -x c++ -c - -o test_dl.o 2>/dev/null <<EOF
|
||||
void dummy_func() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
$CXX $COMMON_FLAGS $PLATFORM_SHARED_LDFLAGS test_dl.o -o test.o 2>/dev/null
|
||||
$CXX $COMMON_FLAGS $PLATFORM_SHARED_LDFLAGS test_dl.o -o /dev/null 2>/dev/null
|
||||
if [ "$?" = 0 ]; then
|
||||
EXEC_LDFLAGS+="-ldl"
|
||||
rm -f test_dl.o
|
||||
@@ -825,27 +831,6 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# check for F_FULLFSYNC
|
||||
$CXX $PLATFORM_CXXFALGS -x c++ - -o test.o 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
int main() {
|
||||
fcntl(0, F_FULLFSYNC);
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DHAVE_FULLFSYNC"
|
||||
fi
|
||||
|
||||
rm -f test.o test_dl.o
|
||||
|
||||
# Get the path for the folly installation dir
|
||||
if [ "$USE_FOLLY" ]; then
|
||||
if [ "$FOLLY_DIR" ]; then
|
||||
FOLLY_PATH=`cd $FOLLY_DIR && $PYTHON build/fbcode_builder/getdeps.py show-inst-dir folly`
|
||||
fi
|
||||
fi
|
||||
|
||||
PLATFORM_CCFLAGS="$PLATFORM_CCFLAGS $COMMON_FLAGS"
|
||||
PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS $COMMON_FLAGS"
|
||||
|
||||
@@ -885,8 +870,6 @@ echo "CLANG_ANALYZER=$CLANG_ANALYZER" >> "$OUTPUT"
|
||||
echo "PROFILING_FLAGS=$PROFILING_FLAGS" >> "$OUTPUT"
|
||||
echo "FIND=$FIND" >> "$OUTPUT"
|
||||
echo "WATCH=$WATCH" >> "$OUTPUT"
|
||||
echo "FOLLY_PATH=$FOLLY_PATH" >> "$OUTPUT"
|
||||
|
||||
# This will enable some related identifiers for the preprocessor
|
||||
if test -n "$JEMALLOC"; then
|
||||
echo "JEMALLOC=1" >> "$OUTPUT"
|
||||
@@ -898,8 +881,8 @@ if test -n "$WITH_JEMALLOC_FLAG"; then
|
||||
echo "WITH_JEMALLOC_FLAG=$WITH_JEMALLOC_FLAG" >> "$OUTPUT"
|
||||
fi
|
||||
echo "LUA_PATH=$LUA_PATH" >> "$OUTPUT"
|
||||
if test -n "$USE_FOLLY"; then
|
||||
echo "USE_FOLLY=$USE_FOLLY" >> "$OUTPUT"
|
||||
if test -n "$USE_FOLLY_DISTRIBUTED_MUTEX"; then
|
||||
echo "USE_FOLLY_DISTRIBUTED_MUTEX=$USE_FOLLY_DISTRIBUTED_MUTEX" >> "$OUTPUT"
|
||||
fi
|
||||
if test -n "$PPC_LIBC_IS_GNU"; then
|
||||
echo "PPC_LIBC_IS_GNU=$PPC_LIBC_IS_GNU" >> "$OUTPUT"
|
||||
|
||||
@@ -5,44 +5,24 @@
|
||||
|
||||
BAD=""
|
||||
|
||||
git grep -n 'namespace rocksdb' -- '*.[ch]*'
|
||||
git grep 'namespace rocksdb' -- '*.[ch]*'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ Do not hardcode namespace rocksdb. Use ROCKSDB_NAMESPACE"
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -n -i 'nocommit' -- ':!build_tools/check-sources.sh'
|
||||
git grep -i 'nocommit' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ Code was not intended to be committed"
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -n 'include <rocksdb/' -- ':!build_tools/check-sources.sh'
|
||||
git grep '<rocksdb/' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^^ Use double-quotes as in #include "rocksdb/something.h"'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -n 'include "include/rocksdb/' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^^ Use #include "rocksdb/something.h" instead of #include "include/rocksdb/something.h"'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -n 'using namespace' -- ':!build_tools' ':!docs' \
|
||||
':!third-party/folly/folly/lang/Align.h' \
|
||||
':!third-party/gtest-1.8.1/fused-src/gtest/gtest.h'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^ Do not use "using namespace"'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -n -P "[\x80-\xFF]" -- ':!docs' ':!*.md'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^ Use only ASCII characters in source files'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
if [ "$BAD" ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,19 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/7331085db891a2ef4a88a48a751d834e8d68f4cb/5.x/centos7-native/c447969
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/1bd23f9917738974ad0ff305aa23eb5f93f18305/9.0.0/centos7-native/c9f9104
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/6ace84e956873d53638c738b6f65f3f469cca74c/5.x/gcc-5-glibc-2.23/339d858
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/192b0f42d63dcf6210d6ceae387b49af049e6e0c/2.23/gcc-5-glibc-2.23/ca1d1c0
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/gcc-5-glibc-2.23/9bc6787
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2d9f0b9a4274cc21f61272a9e89bdb859bce8f1f/1.2.8/gcc-5-glibc-2.23/9bc6787
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/gcc-5-glibc-2.23/9bc6787
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0f607f8fc442ea7d6b876931b1898bb573d5e5da/1.9.1/gcc-5-glibc-2.23/9bc6787
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/ca22bc441a4eb709e9e0b1f9fec9750fed7b31c5/1.4.x/gcc-5-glibc-2.23/03859b5
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/gcc-5-glibc-2.23/9bc6787
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/c26f08f47ac35fc31da2633b7da92d6b863246eb/master/gcc-5-glibc-2.23/0c8f76d
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/3f3fb57a5ccc5fd21c66416c0b83e0aa76a05376/2.0.11/gcc-5-glibc-2.23/9bc6787
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/40c73d874898b386a71847f1b99115d93822d11f/1.4/gcc-5-glibc-2.23/b443de1
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/4ce8e8dba77cdbd81b75d6f0c32fd7a1b76a11ec/2018_U5/gcc-5-glibc-2.23/9bc6787
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/fb251ecd2f5ae16f8671f7014c246e52a748fe0b/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/2e3cb7d119b3cea5f1e738cc13a1ac69f49eb875/2.29.1/centos7-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d42d152a15636529b0861ec493927200ebebca8e/3.15.0/gcc-5-glibc-2.23/9bc6787
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.2.3/gcc-5-glibc-2.23/65372bd
|
||||
@@ -0,0 +1,20 @@
|
||||
# shellcheck disable=SC2148
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/cf7d14c625ce30bae1a4661c2319c5a283e4dd22/4.8.1/centos6-native/cc6c9dc
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/8598c375b0e94e1448182eb3df034704144a838d/stable/centos6-native/3f16ddd
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d6e0a7da6faba45f5e5b1638f9edd7afc2f34e7d/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/d282e6e8f3d20f4e40a516834847bdc038e07973/2.17/gcc-4.8.1-glibc-2.17/99df8fc
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/8c38a4c1e52b4c2cc8a9cdc31b9c947ed7dbfcb4/1.1.3/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/0882df3713c7a84f15abe368dc004581f20b39d7/1.2.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/740325875f6729f42d28deaa2147b0854f3a347e/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0e790b441e2d9acd68d51e1d2e028f88c6a79ddf/r131/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/9455f75ff7f4831dc9fda02a6a0f8c68922fad8f/1.0.0/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/f001a51b2854957676d07306ef3abf67186b5c8b/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/fc8a13ca1fffa4d0765c716c5a0b49f0c107518f/master/gcc-4.8.1-glibc-2.17/8d31e51
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/17c514c4d102a25ca15f4558be564eeed76f4b6a/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/ad576de2a1ea560c4d3434304f0fc4e079bede42/trunk/gcc-4.8.1-glibc-2.17/675d945
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/9d9a554877d0c5bef330fe818ab7178806dd316a/4.0_update2/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/7c111ff27e0c466235163f00f280a9d617c3d2ec/4.0.9-36_fbk5_2933_gd092e3f/gcc-4.8.1-glibc-2.17/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/b7fd454c4b10c6a81015d4524ed06cdeab558490/2.26/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d7f4d4d86674a57668e3a96f76f0e17dd0eb8765/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/61e4abf5813bbc39bc4f548757ccfcadde175a48/5.2.3/centos6-native/730f94e
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/7331085db891a2ef4a88a48a751d834e8d68f4cb/7.x/centos7-native/b2ef2b6
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/963d9aeda70cc4779885b1277484fe7544a04e3e/9.0.0/platform007/9e92d53/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/6ace84e956873d53638c738b6f65f3f469cca74c/7.x/platform007/5620abc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/192b0f42d63dcf6210d6ceae387b49af049e6e0c/2.26/platform007/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/7f9bdaada18f59bc27ec2b0871eb8a6144343aef/1.1.3/platform007/ca4da3d
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/2d9f0b9a4274cc21f61272a9e89bdb859bce8f1f/1.2.8/platform007/ca4da3d
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/dc49a21c5fceec6456a7a28a94dcd16690af1337/1.0.6/platform007/ca4da3d
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0f607f8fc442ea7d6b876931b1898bb573d5e5da/1.9.1/platform007/ca4da3d
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/ca22bc441a4eb709e9e0b1f9fec9750fed7b31c5/1.4.x/platform007/15a3614
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/0b9929d2588991c65a57168bf88aff2db87c5d48/2.2.0/platform007/ca4da3d
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/c26f08f47ac35fc31da2633b7da92d6b863246eb/master/platform007/c26c002
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/3f3fb57a5ccc5fd21c66416c0b83e0aa76a05376/2.0.11/platform007/ca4da3d
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/40c73d874898b386a71847f1b99115d93822d11f/1.4/platform007/6f3e0a9
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/4ce8e8dba77cdbd81b75d6f0c32fd7a1b76a11ec/2018_U5/platform007/ca4da3d
|
||||
LIBURING_BASE=/mnt/gvfs/third-party2/liburing/79427253fd0d42677255aacfe6d13bfe63f752eb/20190828/platform007/ca4da3d
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/fb251ecd2f5ae16f8671f7014c246e52a748fe0b/fb/platform007/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/ab9f09bba370e7066cafd4eb59752db93f2e8312/2.29.1/platform007/15a3614
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d42d152a15636529b0861ec493927200ebebca8e/3.15.0/platform007/ca4da3d
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/f0cd714433206d5139df61659eb7b28b1dea6683/5.3.4/platform007/5007832
|
||||
@@ -6,10 +6,10 @@ GLIBC_BASE=/mnt/gvfs/third-party2/glibc/45ce3375cdc77ecb2520bbf8f0ecddd3f98efd7a
|
||||
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
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/ec6573523b0ce55ef6373a4801189027cf07bb2c/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
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/d9aef9feb850b168a68736420f217b01cce11a89/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
|
||||
@@ -18,5 +18,4 @@ KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/32b8a2407b634df3f8f948
|
||||
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
|
||||
BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/bce8d9564eaf161700aa3a20b1051564acf555fb/1.5.5/platform009/7f3b187
|
||||
@@ -1,22 +0,0 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
# The file is generated using update_dependencies.sh.
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/e40bde78650fa91b8405a857e3f10bf336633fb0/11.x/centos7-native/886b5eb
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/2043340983c032915adbb6f78903dc855b65aee8/12/platform010/9520e0f
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/c00dcc6a3e4125c7e8b248e9a79c14b78ac9e0ca/11.x/platform010/5684a5a
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/0b9c8e4b060eda62f3bc1c6127bbe1256697569b/2.34/platform010/f259413
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/bc9647f7912b131315827d65cb6189c21f381d05/1.1.3/platform010/76ebdda
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/a6f5f3f1d063d2d00cd02fc12f0f05fc3ab3a994/1.2.11/platform010/76ebdda
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/09703139cfc376bd8a82642385a0e97726b28287/1.0.6/platform010/76ebdda
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/60220d6a5bf7722b9cc239a1368c596619b12060/1.9.1/platform010/76ebdda
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/50eace8143eaaea9473deae1f3283e0049e05633/1.4.x/platform010/64091f4
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/5d27e5919771603da06000a027b12f799e58a4f7/2.2.0/platform010/76ebdda
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/b62912d333ef33f9760efa6219dbe3fe6abb3b0e/master/platform010/f57cc4a
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/6b412770957aa3c8a87e5e0dcd8cc2f45f393bc0/2.0.11/platform010/76ebdda
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/52f69816e936e147664ad717eb71a1a0e9dc973a/1.4/platform010/5074a48
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/c9cc192099fa84c0dcd0ffeedd44a373ad6e4925/2018_U5/platform010/76ebdda
|
||||
LIBURING_BASE=/mnt/gvfs/third-party2/liburing/a98e2d137007e3ebf7f33bd6f99c2c56bdaf8488/20210212/platform010/76ebdda
|
||||
BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/780c7a0f9cf0967961e69ad08e61cddd85d61821/trunk/platform010/76ebdda
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/02d9f76aaaba580611cf75e741753c800c7fdc12/fb/platform010/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/938dc3f064ef3a48c0446f5b11d788d50b3eb5ee/2.37/centos7-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/429a6b3203eb415f1599bd15183659153129188e/3.15.0/platform010/76ebdda
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/363787fa5cac2a8aa20638909210443278fa138e/5.3.4/platform010/9079c97
|
||||
@@ -21,48 +21,38 @@ LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SNAPPY; then
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
if ! test $ROCKSDB_DISABLE_ZLIB; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
fi
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BZIP; then
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
fi
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_LZ4; then
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZSTD; then
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD -DZSTD_STATIC_LINKING_ONLY"
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD -DZSTD_STATIC_LINKING_ONLY"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
@@ -147,7 +137,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 -DHAVE_SSE42"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
@@ -172,4 +162,6 @@ else
|
||||
LUA_LIB=" $LUA_PATH/lib/liblua_pic.a"
|
||||
fi
|
||||
|
||||
USE_FOLLY_DISTRIBUTED_MUTEX=1
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -0,0 +1,120 @@
|
||||
#!/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++ compiler and also
|
||||
# uses jemalloc
|
||||
|
||||
BASEDIR=`dirname $BASH_SOURCE`
|
||||
source "$BASEDIR/dependencies_4.8.1.sh"
|
||||
|
||||
# location of libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# location of glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# location of snappy headers and libraries
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include"
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
|
||||
# location of bzip headers and libraries
|
||||
BZIP2_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP2_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include"
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include"
|
||||
JEMALLOC_LIB="$JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
|
||||
# location of tbb
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
|
||||
test "$USE_SSE" || USE_SSE=1
|
||||
export USE_SSE
|
||||
test "$PORTABLE" || PORTABLE=1
|
||||
export PORTABLE
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP2_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
if [ -z "$USE_CLANG" ]; then
|
||||
# gcc
|
||||
CC="$GCC_BASE/bin/gcc"
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
CXX="$GCC_BASE/bin/gcc-ar"
|
||||
|
||||
CFLAGS="-B$BINUTILS/gold -m64 -mtune=generic"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
CLANG_LIB="$CLANG_BASE/lib"
|
||||
CLANG_INCLUDE="$CLANG_LIB/clang/*/include"
|
||||
CC="$CLANG_BIN/clang"
|
||||
CXX="$CLANG_BIN/clang++"
|
||||
AR="$CLANG_BIN/llvm-ar"
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include/"
|
||||
|
||||
CFLAGS="-B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.8.1 "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/4.8.1/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 "
|
||||
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 -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42"
|
||||
CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2 -DLZ4 -DZSTD -DNUMA -DTBB"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
LUA_PATH="$LUA_BASE"
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE LUA_PATH
|
||||
@@ -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_platform007.sh"
|
||||
|
||||
CFLAGS=""
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/7.3.0"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.a"
|
||||
CFLAGS+=" -DZLIB"
|
||||
|
||||
# location of bzip headers and libraries
|
||||
BZIP_INCLUDE=" -I $BZIP2_BASE/include/"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.a"
|
||||
CFLAGS+=" -DBZIP2"
|
||||
|
||||
LZ4_INCLUDE=" -I $LZ4_BASE/include/"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.a"
|
||||
CFLAGS+=" -DLZ4"
|
||||
fi
|
||||
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
else
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
fi
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
else
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# location of LIBURING
|
||||
LIBURING_INCLUDE=" -isystem $LIBURING_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
LIBURING_LIBS="$LIBURING_BASE/lib/liburing.a"
|
||||
else
|
||||
LIBURING_LIBS="$LIBURING_BASE/lib/liburing_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DLIBURING"
|
||||
|
||||
test "$USE_SSE" || USE_SSE=1
|
||||
export USE_SSE
|
||||
test "$PORTABLE" || PORTABLE=1
|
||||
export PORTABLE
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_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/tools/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/gold"
|
||||
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/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/7.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 -DROCKSDB_SUPPORT_THREAD_LOCAL -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"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform007/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform007/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_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 CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -14,83 +14,92 @@ 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_INCLUDE="$LIBGCC_BASE/include/c++/9.3.0"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
|
||||
# snappy
|
||||
SNAPPY_INCLUDE=" -I $SNAPPY_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
MAYBE_PIC=
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy.a"
|
||||
else
|
||||
MAYBE_PIC=_pic
|
||||
SNAPPY_LIBS=" $SNAPPY_BASE/lib/libsnappy_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DSNAPPY"
|
||||
|
||||
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
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of zlib headers and libraries
|
||||
ZLIB_INCLUDE=" -I $ZLIB_BASE/include/"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz${MAYBE_PIC}.a"
|
||||
ZLIB_LIBS=" $ZLIB_BASE/lib/libz.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"
|
||||
BZIP_LIBS=" $BZIP2_BASE/lib/libbz2.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"
|
||||
LZ4_LIBS=" $LZ4_BASE/lib/liblz4.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"
|
||||
ZSTD_INCLUDE=" -I $ZSTD_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd.a"
|
||||
else
|
||||
ZSTD_LIBS=" $ZSTD_BASE/lib/libzstd_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DZSTD"
|
||||
|
||||
# location of gflags headers and libraries
|
||||
GFLAGS_INCLUDE=" -I $GFLAGS_BASE/include/"
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags${MAYBE_PIC}.a"
|
||||
if test -z $PIC_BUILD; then
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags.a"
|
||||
else
|
||||
GFLAGS_LIBS=" $GFLAGS_BASE/lib/libgflags_pic.a"
|
||||
fi
|
||||
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"
|
||||
if test -z $PIC_BUILD; then
|
||||
BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark.a"
|
||||
else
|
||||
BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark_pic.a"
|
||||
fi
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc${MAYBE_PIC}.a"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma${MAYBE_PIC}.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
if test -z $PIC_BUILD; then
|
||||
# location of numa
|
||||
NUMA_INCLUDE=" -I $NUMA_BASE/include/"
|
||||
NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
CFLAGS+=" -DNUMA"
|
||||
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind${MAYBE_PIC}.a"
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
fi
|
||||
|
||||
# location of TBB
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb${MAYBE_PIC}.a"
|
||||
if test -z $PIC_BUILD; then
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
else
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DTBB"
|
||||
|
||||
# location of LIBURING
|
||||
LIBURING_INCLUDE=" -isystem $LIBURING_BASE/include/"
|
||||
LIBURING_LIBS="$LIBURING_BASE/lib/liburing${MAYBE_PIC}.a"
|
||||
if test -z $PIC_BUILD; then
|
||||
LIBURING_LIBS="$LIBURING_BASE/lib/liburing.a"
|
||||
else
|
||||
LIBURING_LIBS="$LIBURING_BASE/lib/liburing_pic.a"
|
||||
fi
|
||||
CFLAGS+=" -DLIBURING"
|
||||
|
||||
test "$USE_SSE" || USE_SSE=1
|
||||
@@ -102,7 +111,7 @@ 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"
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
@@ -145,7 +154,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 -DHAVE_SSE42 -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 -DROCKSDB_SUPPORT_THREAD_LOCAL -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"
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
#!/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_platform010.sh"
|
||||
|
||||
# Disallow using libraries from default locations as they might not be compatible with platform010 libraries.
|
||||
CFLAGS=" --sysroot=/DOES/NOT/EXIST"
|
||||
|
||||
# libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include/c++/trunk"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib -B$LIBGCC_BASE/lib/gcc/x86_64-facebook-linux/trunk/"
|
||||
|
||||
# glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
GLIBC_LIBS=" -L $GLIBC_BASE/lib"
|
||||
GLIBC_LIBS+=" -B$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"
|
||||
|
||||
# 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"
|
||||
|
||||
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 -nostdinc -nostdlib"
|
||||
CFLAGS+=" -I$GCC_BASE/include"
|
||||
CFLAGS+=" -isystem $GCC_BASE/lib/gcc/x86_64-redhat-linux-gnu/11.2.1/include"
|
||||
CFLAGS+=" -isystem $GCC_BASE/lib/gcc/x86_64-redhat-linux-gnu/11.2.1/install-tools/include"
|
||||
CFLAGS+=" -isystem $GCC_BASE/lib/gcc/x86_64-redhat-linux-gnu/11.2.1/include-fixed/"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -I$GLIBC_INCLUDE"
|
||||
CFLAGS+=" -I$LIBGCC_BASE/include"
|
||||
CFLAGS+=" -I$LIBGCC_BASE/include/c++/11.x/"
|
||||
CFLAGS+=" -I$LIBGCC_BASE/include/c++/11.x/x86_64-facebook-linux/"
|
||||
CFLAGS+=" -I$LIBGCC_BASE/include/c++/11.x/backward"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE -I$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"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/trunk "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/trunk/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $CLANG_INCLUDE"
|
||||
CFLAGS+=" -Wno-expansion-to-defined "
|
||||
CXXFLAGS="-nostdinc++"
|
||||
fi
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
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 -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/platform010/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform010/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/"
|
||||
|
||||
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
|
||||
@@ -127,11 +127,11 @@ uncommitted_code=`git diff HEAD`
|
||||
if [ -z "$uncommitted_code" ]
|
||||
then
|
||||
# Attempt to get name of facebook/rocksdb.git remote.
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(LC_ALL=POSIX LANG=POSIX git remote -v | grep 'facebook/rocksdb.git' | head -n 1 | cut -f 1)"
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(git remote -v | grep 'facebook/rocksdb.git' | head -n 1 | cut -f 1)"
|
||||
# Fall back on 'origin' if that fails
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE=origin
|
||||
# Use main branch from that remote
|
||||
[ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/$(LC_ALL=POSIX LANG=POSIX git remote show $FORMAT_REMOTE | sed -n '/HEAD branch/s/.*: //p')"
|
||||
[ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/$(git remote show $FORMAT_REMOTE | sed -n '/HEAD branch/s/.*: //p')"
|
||||
# Get the common ancestor with that remote branch. Everything after that
|
||||
# common ancestor would be considered the contents of a pull request, so
|
||||
# should be relevant for formatting fixes.
|
||||
@@ -170,6 +170,11 @@ echo "$diffs" |
|
||||
sed -e "s/\(^-.*$\)/`echo -e \"$COLOR_RED\1$COLOR_END\"`/" |
|
||||
sed -e "s/\(^+.*$\)/`echo -e \"$COLOR_GREEN\1$COLOR_END\"`/"
|
||||
|
||||
if [[ "$OPT" == *"-DTRAVIS"* ]]
|
||||
then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "Would you like to fix the format automatically (y/n): \c"
|
||||
|
||||
# Make sure under any mode, we can read user input.
|
||||
|
||||
@@ -1561,7 +1561,6 @@ sub save_stdin_stdout_stderr {
|
||||
::die_bug("Can't dup STDERR: $!");
|
||||
open $Global::original_stdin, "<&", "STDIN" or
|
||||
::die_bug("Can't dup STDIN: $!");
|
||||
$Global::is_terminal = (-t $Global::original_stderr) && !$ENV{'CIRCLECI'} && !$ENV{'TRAVIS'};
|
||||
}
|
||||
|
||||
sub enough_file_handles {
|
||||
@@ -1841,17 +1840,12 @@ sub start_another_job {
|
||||
}
|
||||
}
|
||||
|
||||
$opt::min_progress_interval = 0;
|
||||
|
||||
sub init_progress {
|
||||
# Uses:
|
||||
# $opt::bar
|
||||
# Returns:
|
||||
# list of computers for progress output
|
||||
$|=1;
|
||||
if (not $Global::is_terminal) {
|
||||
$opt::min_progress_interval = 30;
|
||||
}
|
||||
if($opt::bar) {
|
||||
return("","");
|
||||
}
|
||||
@@ -1876,9 +1870,6 @@ sub drain_job_queue {
|
||||
}
|
||||
my $last_header="";
|
||||
my $sleep = 0.2;
|
||||
my $last_left = 1000000000;
|
||||
my $last_progress_time = 0;
|
||||
my $ps_reported = 0;
|
||||
do {
|
||||
while($Global::total_running > 0) {
|
||||
debug($Global::total_running, "==", scalar
|
||||
@@ -1889,38 +1880,14 @@ sub drain_job_queue {
|
||||
close $job->fh(0,"w");
|
||||
}
|
||||
}
|
||||
# When not connected to terminal, assume CI (e.g. CircleCI). In
|
||||
# that case we want occasional progress output to prevent abort
|
||||
# due to timeout with no output, but we also need to stop sending
|
||||
# progress output if there has been no actual progress, so that
|
||||
# the job can time out appropriately (CirecleCI: 10m) in case of
|
||||
# a hung test. But without special output, it is extremely
|
||||
# annoying to diagnose which test is hung, so we add that using
|
||||
# `ps` below.
|
||||
if($opt::progress and
|
||||
($Global::is_terminal or (time() - $last_progress_time) >= 30)) {
|
||||
if($opt::progress) {
|
||||
my %progress = progress();
|
||||
if($last_header ne $progress{'header'}) {
|
||||
print $Global::original_stderr "\n", $progress{'header'}, "\n";
|
||||
$last_header = $progress{'header'};
|
||||
}
|
||||
if ($Global::is_terminal) {
|
||||
print $Global::original_stderr "\r",$progress{'status'};
|
||||
}
|
||||
if ($last_left > $Global::left) {
|
||||
if (not $Global::is_terminal) {
|
||||
print $Global::original_stderr $progress{'status'},"\n";
|
||||
}
|
||||
$last_progress_time = time();
|
||||
$ps_reported = 0;
|
||||
} elsif (not $ps_reported and (time() - $last_progress_time) >= 60) {
|
||||
# No progress in at least 60 seconds: run ps
|
||||
print $Global::original_stderr "\n";
|
||||
system("ps", "-wf");
|
||||
$ps_reported = 1;
|
||||
}
|
||||
$last_left = $Global::left;
|
||||
flush $Global::original_stderr;
|
||||
print $Global::original_stderr "\r",$progress{'status'};
|
||||
flush $Global::original_stderr;
|
||||
}
|
||||
if($Global::total_running < $Global::max_jobs_running
|
||||
and not $Global::JobQueue->empty()) {
|
||||
@@ -1954,7 +1921,7 @@ sub drain_job_queue {
|
||||
not $Global::start_no_new_jobs and not $Global::JobQueue->empty());
|
||||
if($opt::progress) {
|
||||
my %progress = progress();
|
||||
print $Global::original_stderr $opt::progress_sep, $progress{'status'}, "\n";
|
||||
print $Global::original_stderr "\r", $progress{'status'}, "\n";
|
||||
flush $Global::original_stderr;
|
||||
}
|
||||
}
|
||||
@@ -1987,11 +1954,10 @@ sub progress {
|
||||
my $eta = "";
|
||||
my ($status,$header)=("","");
|
||||
if($opt::eta) {
|
||||
my($total, $completed, $left, $pctcomplete, $avgtime, $this_eta) =
|
||||
compute_eta();
|
||||
$eta = sprintf("ETA: %ds Left: %d AVG: %.2fs ",
|
||||
$this_eta, $left, $avgtime);
|
||||
$Global::left = $left;
|
||||
my($total, $completed, $left, $pctcomplete, $avgtime, $this_eta) =
|
||||
compute_eta();
|
||||
$eta = sprintf("ETA: %ds Left: %d AVG: %.2fs ",
|
||||
$this_eta, $left, $avgtime);
|
||||
}
|
||||
my $termcols = terminal_columns();
|
||||
my @workers = sort keys %Global::host;
|
||||
|
||||
Executable
+209
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python2.7
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import argparse
|
||||
import commands
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
#
|
||||
# Simple logger
|
||||
#
|
||||
|
||||
class Log:
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.f = open(self.filename, 'w+', 0)
|
||||
|
||||
def caption(self, str):
|
||||
line = "\n##### %s #####\n" % str
|
||||
if self.f:
|
||||
self.f.write("%s \n" % line)
|
||||
else:
|
||||
print(line)
|
||||
|
||||
def error(self, str):
|
||||
data = "\n\n##### ERROR ##### %s" % str
|
||||
if self.f:
|
||||
self.f.write("%s \n" % data)
|
||||
else:
|
||||
print(data)
|
||||
|
||||
def log(self, str):
|
||||
if self.f:
|
||||
self.f.write("%s \n" % str)
|
||||
else:
|
||||
print(str)
|
||||
|
||||
#
|
||||
# Shell Environment
|
||||
#
|
||||
|
||||
|
||||
class Env(object):
|
||||
|
||||
def __init__(self, logfile, tests):
|
||||
self.tests = tests
|
||||
self.log = Log(logfile)
|
||||
|
||||
def shell(self, cmd, path=os.getcwd()):
|
||||
if path:
|
||||
os.chdir(path)
|
||||
|
||||
self.log.log("==== shell session ===========================")
|
||||
self.log.log("%s> %s" % (path, cmd))
|
||||
status = subprocess.call("cd %s; %s" % (path, cmd), shell=True,
|
||||
stdout=self.log.f, stderr=self.log.f)
|
||||
self.log.log("status = %s" % status)
|
||||
self.log.log("============================================== \n\n")
|
||||
return status
|
||||
|
||||
def GetOutput(self, cmd, path=os.getcwd()):
|
||||
if path:
|
||||
os.chdir(path)
|
||||
|
||||
self.log.log("==== shell session ===========================")
|
||||
self.log.log("%s> %s" % (path, cmd))
|
||||
status, out = commands.getstatusoutput(cmd)
|
||||
self.log.log("status = %s" % status)
|
||||
self.log.log("out = %s" % out)
|
||||
self.log.log("============================================== \n\n")
|
||||
return status, out
|
||||
|
||||
#
|
||||
# Pre-commit checker
|
||||
#
|
||||
|
||||
|
||||
class PreCommitChecker(Env):
|
||||
|
||||
def __init__(self, args):
|
||||
Env.__init__(self, args.logfile, args.tests)
|
||||
self.ignore_failure = args.ignore_failure
|
||||
|
||||
#
|
||||
# Get commands for a given job from the determinator file
|
||||
#
|
||||
def get_commands(self, test):
|
||||
status, out = self.GetOutput(
|
||||
"RATIO=1 build_tools/rocksdb-lego-determinator %s" % test, ".")
|
||||
return status, out
|
||||
|
||||
#
|
||||
# Run a specific CI job
|
||||
#
|
||||
def run_test(self, test):
|
||||
self.log.caption("Running test %s locally" % test)
|
||||
|
||||
# get commands for the CI job determinator
|
||||
status, cmds = self.get_commands(test)
|
||||
if status != 0:
|
||||
self.log.error("Error getting commands for test %s" % test)
|
||||
return False
|
||||
|
||||
# Parse the JSON to extract the commands to run
|
||||
cmds = re.findall("'shell':'([^\']*)'", cmds)
|
||||
|
||||
if len(cmds) == 0:
|
||||
self.log.log("No commands found")
|
||||
return False
|
||||
|
||||
# Run commands
|
||||
for cmd in cmds:
|
||||
# Replace J=<..> with the local environment variable
|
||||
if "J" in os.environ:
|
||||
cmd = cmd.replace("J=1", "J=%s" % os.environ["J"])
|
||||
cmd = cmd.replace("make ", "make -j%s " % os.environ["J"])
|
||||
# Run the command
|
||||
status = self.shell(cmd, ".")
|
||||
if status != 0:
|
||||
self.log.error("Error running command %s for test %s"
|
||||
% (cmd, test))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
#
|
||||
# Run specified CI jobs
|
||||
#
|
||||
def run_tests(self):
|
||||
if not self.tests:
|
||||
self.log.error("Invalid args. Please provide tests")
|
||||
return False
|
||||
|
||||
self.print_separator()
|
||||
self.print_row("TEST", "RESULT")
|
||||
self.print_separator()
|
||||
|
||||
result = True
|
||||
for test in self.tests:
|
||||
start_time = time.time()
|
||||
self.print_test(test)
|
||||
result = self.run_test(test)
|
||||
elapsed_min = (time.time() - start_time) / 60
|
||||
if not result:
|
||||
self.log.error("Error running test %s" % test)
|
||||
self.print_result("FAIL (%dm)" % elapsed_min)
|
||||
if not self.ignore_failure:
|
||||
return False
|
||||
result = False
|
||||
else:
|
||||
self.print_result("PASS (%dm)" % elapsed_min)
|
||||
|
||||
self.print_separator()
|
||||
return result
|
||||
|
||||
#
|
||||
# Print a line
|
||||
#
|
||||
def print_separator(self):
|
||||
print("".ljust(60, "-"))
|
||||
|
||||
#
|
||||
# Print two colums
|
||||
#
|
||||
def print_row(self, c0, c1):
|
||||
print("%s%s" % (c0.ljust(40), c1.ljust(20)))
|
||||
|
||||
def print_test(self, test):
|
||||
print(test.ljust(40), end="")
|
||||
sys.stdout.flush()
|
||||
|
||||
def print_result(self, result):
|
||||
print(result.ljust(20))
|
||||
|
||||
#
|
||||
# Main
|
||||
#
|
||||
parser = argparse.ArgumentParser(description='RocksDB pre-commit checker.')
|
||||
|
||||
# --log <logfile>
|
||||
parser.add_argument('--logfile', default='/tmp/precommit-check.log',
|
||||
help='Log file. Default is /tmp/precommit-check.log')
|
||||
# --ignore_failure
|
||||
parser.add_argument('--ignore_failure', action='store_true', default=False,
|
||||
help='Stop when an error occurs')
|
||||
# <test ....>
|
||||
parser.add_argument('tests', nargs='+',
|
||||
help='CI test(s) to run. e.g: unit punit asan tsan ubsan')
|
||||
|
||||
args = parser.parse_args()
|
||||
checker = PreCommitChecker(args)
|
||||
|
||||
print("Please follow log %s" % checker.log.filename)
|
||||
|
||||
if not checker.run_tests():
|
||||
print("Error running tests. Please check log file %s"
|
||||
% checker.log.filename)
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
@@ -258,6 +258,7 @@ common_in_mem_args="--db=/dev/shm/rocksdb \
|
||||
--value_size=100 \
|
||||
--compression_type=none \
|
||||
--compression_ratio=1 \
|
||||
--hard_rate_limit=2 \
|
||||
--write_buffer_size=134217728 \
|
||||
--max_write_buffer_number=4 \
|
||||
--level0_file_num_compaction_trigger=8 \
|
||||
|
||||
Executable
+1361
File diff suppressed because it is too large.
Load diff
@@ -42,7 +42,7 @@ $RunOnly.Add("c_test") | Out-Null
|
||||
$RunOnly.Add("compact_on_deletion_collector_test") | Out-Null
|
||||
$RunOnly.Add("merge_test") | Out-Null
|
||||
$RunOnly.Add("stringappend_test") | Out-Null # Apparently incorrectly written
|
||||
$RunOnly.Add("backup_engine_test") | Out-Null # Disabled
|
||||
$RunOnly.Add("backupable_db_test") | Out-Null # Disabled
|
||||
$RunOnly.Add("timer_queue_test") | Out-Null # Not a gtest
|
||||
|
||||
if($RunAll -and $SuiteRun -ne "") {
|
||||
@@ -491,3 +491,5 @@ if(!$script:success) {
|
||||
}
|
||||
|
||||
exit 0
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
# from official ubuntu 20.04
|
||||
FROM ubuntu:20.04
|
||||
# update system
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
# install basic tools
|
||||
RUN apt-get install -y vim wget curl
|
||||
# install tzdata noninteractive
|
||||
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
|
||||
# install git and default compilers
|
||||
RUN apt-get install -y git gcc g++ clang clang-tools
|
||||
# install basic package
|
||||
RUN apt-get install -y lsb-release software-properties-common gnupg
|
||||
# install gflags, tbb
|
||||
RUN apt-get install -y libgflags-dev libtbb-dev
|
||||
# install compression libs
|
||||
RUN apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
|
||||
# install cmake
|
||||
RUN apt-get install -y cmake
|
||||
# install clang-13
|
||||
WORKDIR /root
|
||||
RUN wget https://apt.llvm.org/llvm.sh
|
||||
RUN chmod +x llvm.sh
|
||||
RUN ./llvm.sh 13 all
|
||||
# install gcc-7, 8, 10, 11, default is 9
|
||||
RUN apt-get install -y gcc-7 g++-7
|
||||
RUN apt-get install -y gcc-8 g++-8
|
||||
RUN apt-get install -y gcc-10 g++-10
|
||||
RUN add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
RUN apt-get install -y gcc-11 g++-11
|
||||
# install apt-get install -y valgrind
|
||||
RUN apt-get install -y valgrind
|
||||
# install folly depencencies
|
||||
RUN apt-get install -y libgoogle-glog-dev
|
||||
# install openjdk 8
|
||||
RUN apt-get install -y openjdk-8-jdk
|
||||
ENV JAVA_HOME /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
# install mingw
|
||||
RUN apt-get install -y mingw-w64
|
||||
|
||||
# install gtest-parallel package
|
||||
RUN git clone --single-branch --branch master --depth 1 https://github.com/google/gtest-parallel.git ~/gtest-parallel
|
||||
ENV PATH $PATH:/root/gtest-parallel
|
||||
|
||||
# install libprotobuf for fuzzers test
|
||||
RUN apt-get install -y ninja-build binutils liblzma-dev libz-dev pkg-config autoconf libtool
|
||||
RUN git clone --branch v1.0 https://github.com/google/libprotobuf-mutator.git ~/libprotobuf-mutator && cd ~/libprotobuf-mutator && git checkout ffd86a32874e5c08a143019aad1aaf0907294c9f && mkdir build && cd build && cmake .. -GNinja -DCMAKE_C_COMPILER=clang-13 -DCMAKE_CXX_COMPILER=clang++-13 -DCMAKE_BUILD_TYPE=Release -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON && ninja && ninja install
|
||||
ENV PKG_CONFIG_PATH /usr/local/OFF/:/root/libprotobuf-mutator/build/external.protobuf/lib/pkgconfig/
|
||||
ENV PROTOC_BIN /root/libprotobuf-mutator/build/external.protobuf/bin/protoc
|
||||
|
||||
# install the latest google benchmark
|
||||
RUN git clone --depth 1 --branch v1.7.0 https://github.com/google/benchmark.git ~/benchmark
|
||||
RUN cd ~/benchmark && mkdir build && cd build && cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_GTEST_TESTS=0 && ninja && ninja install
|
||||
|
||||
# clean up
|
||||
RUN rm -rf /var/lib/apt/lists/*
|
||||
RUN rm -rf /root/benchmark
|
||||
@@ -9,7 +9,6 @@ OUTPUT=""
|
||||
function log_header()
|
||||
{
|
||||
echo "# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved." >> "$OUTPUT"
|
||||
echo "# The file is generated using update_dependencies.sh." >> "$OUTPUT"
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +18,7 @@ function log_variable()
|
||||
}
|
||||
|
||||
|
||||
TP2_LATEST="/data/users/$USER/fbsource/fbcode/third-party2/"
|
||||
TP2_LATEST="/mnt/vol/engshare/fbcode/third-party2"
|
||||
## $1 => lib name
|
||||
## $2 => lib version (if not provided, will try to pick latest)
|
||||
## $3 => platform (if not provided, will try to pick latest gcc)
|
||||
@@ -51,8 +50,6 @@ function get_lib_base()
|
||||
fi
|
||||
|
||||
result=`ls -1d $result/*/ | head -n1`
|
||||
|
||||
echo Finding link $result
|
||||
|
||||
# lib_name => LIB_NAME_BASE
|
||||
local __res_var=${lib_name^^}"_BASE"
|
||||
@@ -64,10 +61,10 @@ function get_lib_base()
|
||||
}
|
||||
|
||||
###########################################################
|
||||
# platform010 dependencies #
|
||||
# platform007 dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_platform010.sh"
|
||||
OUTPUT="$BASEDIR/dependencies_platform007.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
@@ -75,42 +72,40 @@ touch "$OUTPUT"
|
||||
echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/11.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/12/platform010/*/`
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/7.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/`
|
||||
|
||||
log_header
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 11.x platform010
|
||||
get_lib_base glibc 2.34 platform010
|
||||
get_lib_base snappy LATEST platform010
|
||||
get_lib_base zlib LATEST platform010
|
||||
get_lib_base bzip2 LATEST platform010
|
||||
get_lib_base lz4 LATEST platform010
|
||||
get_lib_base zstd LATEST platform010
|
||||
get_lib_base gflags LATEST platform010
|
||||
get_lib_base jemalloc LATEST platform010
|
||||
get_lib_base numa LATEST platform010
|
||||
get_lib_base libunwind LATEST platform010
|
||||
get_lib_base tbb 2018_U5 platform010
|
||||
get_lib_base liburing LATEST platform010
|
||||
get_lib_base benchmark LATEST platform010
|
||||
get_lib_base libgcc 7.x platform007
|
||||
get_lib_base glibc 2.26 platform007
|
||||
get_lib_base snappy LATEST platform007
|
||||
get_lib_base zlib LATEST platform007
|
||||
get_lib_base bzip2 LATEST platform007
|
||||
get_lib_base lz4 LATEST platform007
|
||||
get_lib_base zstd LATEST platform007
|
||||
get_lib_base gflags LATEST platform007
|
||||
get_lib_base jemalloc LATEST platform007
|
||||
get_lib_base numa LATEST platform007
|
||||
get_lib_base libunwind LATEST platform007
|
||||
get_lib_base tbb LATEST platform007
|
||||
get_lib_base liburing LATEST platform007
|
||||
|
||||
get_lib_base kernel-headers fb platform010
|
||||
get_lib_base kernel-headers fb platform007
|
||||
get_lib_base binutils LATEST centos7-native
|
||||
get_lib_base valgrind LATEST platform010
|
||||
get_lib_base lua 5.3.4 platform010
|
||||
get_lib_base valgrind LATEST platform007
|
||||
get_lib_base lua 5.3.4 platform007
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
|
||||
###########################################################
|
||||
# platform009 dependencies #
|
||||
# 5.x dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_platform009.sh"
|
||||
OUTPUT="$BASEDIR/dependencies.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
@@ -118,32 +113,70 @@ 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/*/`
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/5.x/centos7-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos7-native/*/`
|
||||
|
||||
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 libgcc 5.x gcc-5-glibc-2.23
|
||||
get_lib_base glibc 2.23 gcc-5-glibc-2.23
|
||||
get_lib_base snappy LATEST gcc-5-glibc-2.23
|
||||
get_lib_base zlib LATEST gcc-5-glibc-2.23
|
||||
get_lib_base bzip2 LATEST gcc-5-glibc-2.23
|
||||
get_lib_base lz4 LATEST gcc-5-glibc-2.23
|
||||
get_lib_base zstd LATEST gcc-5-glibc-2.23
|
||||
get_lib_base gflags LATEST gcc-5-glibc-2.23
|
||||
get_lib_base jemalloc LATEST gcc-5-glibc-2.23
|
||||
get_lib_base numa LATEST gcc-5-glibc-2.23
|
||||
get_lib_base libunwind LATEST gcc-5-glibc-2.23
|
||||
get_lib_base tbb LATEST gcc-5-glibc-2.23
|
||||
|
||||
get_lib_base kernel-headers fb platform009
|
||||
get_lib_base kernel-headers 4.0.9-36_fbk5_2933_gd092e3f gcc-5-glibc-2.23
|
||||
get_lib_base binutils LATEST centos7-native
|
||||
get_lib_base valgrind LATEST platform009
|
||||
get_lib_base lua 5.3.4 platform009
|
||||
get_lib_base valgrind LATEST gcc-5-glibc-2.23
|
||||
get_lib_base lua 5.2.3 gcc-5-glibc-2.23
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
###########################################################
|
||||
# 4.8.1 dependencies #
|
||||
###########################################################
|
||||
|
||||
OUTPUT="$BASEDIR/dependencies_4.8.1.sh"
|
||||
|
||||
rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
echo "Writing 4.8.1 dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/4.8.1/centos6-native/*/`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos6-native/*/`
|
||||
|
||||
log_header
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 4.8.1 gcc-4.8.1-glibc-2.17
|
||||
get_lib_base glibc 2.17 gcc-4.8.1-glibc-2.17
|
||||
get_lib_base snappy LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base zlib LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base bzip2 LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base lz4 LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base zstd LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base gflags LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base jemalloc LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base numa LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base libunwind LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base tbb 4.0_update2 gcc-4.8.1-glibc-2.17
|
||||
|
||||
get_lib_base kernel-headers LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base valgrind 3.8.1 gcc-4.8.1-glibc-2.17
|
||||
get_lib_base lua 5.2.3 centos6-native
|
||||
|
||||
git diff $OUTPUT
|
||||
Vendored
+2
-59
@@ -33,71 +33,14 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
{offsetof(struct LRUCacheOptions, high_pri_pool_ratio),
|
||||
OptionType::kDouble, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"low_pri_pool_ratio",
|
||||
{offsetof(struct LRUCacheOptions, low_pri_pool_ratio),
|
||||
OptionType::kDouble, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
};
|
||||
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
comp_sec_cache_options_type_info = {
|
||||
{"capacity",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions, capacity),
|
||||
OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"num_shard_bits",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions, num_shard_bits),
|
||||
OptionType::kInt, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"compression_type",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions, compression_type),
|
||||
OptionType::kCompressionType, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"compress_format_version",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions,
|
||||
compress_format_version),
|
||||
OptionType::kUInt32T, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
{"enable_custom_split_merge",
|
||||
{offsetof(struct CompressedSecondaryCacheOptions,
|
||||
enable_custom_split_merge),
|
||||
OptionType::kBoolean, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kMutable}},
|
||||
};
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status SecondaryCache::CreateFromString(
|
||||
const ConfigOptions& config_options, const std::string& value,
|
||||
std::shared_ptr<SecondaryCache>* result) {
|
||||
if (value.find("compressed_secondary_cache://") == 0) {
|
||||
std::string args = value;
|
||||
args.erase(0, std::strlen("compressed_secondary_cache://"));
|
||||
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, "",
|
||||
args, &sec_cache_opts);
|
||||
if (status.ok()) {
|
||||
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, nullptr,
|
||||
result);
|
||||
}
|
||||
return LoadSharedObject<SecondaryCache>(config_options, value, nullptr,
|
||||
result);
|
||||
}
|
||||
|
||||
Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
|
||||
Vendored
+27
-433
@@ -3,18 +3,13 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "cache_key.h"
|
||||
#ifdef GFLAGS
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#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/cache.h"
|
||||
@@ -23,11 +18,8 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
@@ -73,71 +65,13 @@ 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.");
|
||||
|
||||
#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.");
|
||||
|
||||
// ## BEGIN stress_cache_key sub-tool options ##
|
||||
// See class StressCacheKey below.
|
||||
DEFINE_bool(stress_cache_key, false,
|
||||
"If true, run cache key stress test instead");
|
||||
DEFINE_uint32(
|
||||
sck_files_per_day, 2500000,
|
||||
"(-stress_cache_key) Simulated files generated per simulated day");
|
||||
// NOTE: Giving each run a specified lifetime, rather than e.g. "until
|
||||
// first collision" ensures equal skew from start-up, when collisions are
|
||||
// less likely.
|
||||
DEFINE_uint32(sck_days_per_run, 90,
|
||||
"(-stress_cache_key) Number of days to simulate in each run");
|
||||
// NOTE: The number of observed collisions directly affects the relative
|
||||
// accuracy of the predicted probabilities. 15 observations should be well
|
||||
// within factor-of-2 accuracy.
|
||||
DEFINE_uint32(
|
||||
sck_min_collision, 15,
|
||||
"(-stress_cache_key) Keep running until this many collisions seen");
|
||||
// sck_file_size_mb can be thought of as average file size. The simulation is
|
||||
// not precise enough to care about the distribution of file sizes; other
|
||||
// simulations (https://github.com/pdillinger/unique_id/tree/main/monte_carlo)
|
||||
// indicate the distribution only makes a small difference (e.g. < 2x factor)
|
||||
DEFINE_uint32(
|
||||
sck_file_size_mb, 32,
|
||||
"(-stress_cache_key) Simulated file size in MiB, for accounting purposes");
|
||||
DEFINE_uint32(sck_reopen_nfiles, 100,
|
||||
"(-stress_cache_key) Simulate DB re-open average every n files");
|
||||
DEFINE_uint32(sck_newdb_nreopen, 1000,
|
||||
"(-stress_cache_key) Simulate new DB average every n re-opens");
|
||||
DEFINE_uint32(sck_restarts_per_day, 24,
|
||||
"(-stress_cache_key) Average simulated process restarts per day "
|
||||
"(across DBs)");
|
||||
DEFINE_uint32(
|
||||
sck_db_count, 100,
|
||||
"(-stress_cache_key) Parallel DBs in simulation sharing a block cache");
|
||||
DEFINE_uint32(
|
||||
sck_table_bits, 20,
|
||||
"(-stress_cache_key) Log2 number of tracked (live) files (across DBs)");
|
||||
// sck_keep_bits being well below full 128 bits amplifies the collision
|
||||
// probability so that the true probability can be estimated through observed
|
||||
// collisions. (More explanation below.)
|
||||
DEFINE_uint32(
|
||||
sck_keep_bits, 50,
|
||||
"(-stress_cache_key) Number of bits to keep from each cache key (<= 64)");
|
||||
// sck_randomize is used to validate whether cache key is performing "better
|
||||
// than random." Even with this setting, file offsets are not randomized.
|
||||
DEFINE_bool(sck_randomize, false,
|
||||
"(-stress_cache_key) Randomize (hash) cache key");
|
||||
// See https://github.com/facebook/rocksdb/pull/9058
|
||||
DEFINE_bool(sck_footer_unique_id, false,
|
||||
"(-stress_cache_key) Simulate using proposed footer unique id");
|
||||
// ## END stress_cache_key sub-tool options ##
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -222,8 +156,7 @@ struct KeyGen {
|
||||
EncodeFixed64(key_data + 10, key);
|
||||
key_data[18] = char{4};
|
||||
EncodeFixed64(key_data + 19, key);
|
||||
assert(27 >= kCacheKeySize);
|
||||
return Slice(&key_data[off], kCacheKeySize);
|
||||
return Slice(&key_data[off], sizeof(key_data) - off);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -287,24 +220,17 @@ class CacheBench {
|
||||
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 (max_key > (1u << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
if (FLAGS_cache_type == "clock_cache") {
|
||||
fprintf(stderr, "Old clock cache implementation has been removed.\n");
|
||||
exit(1);
|
||||
} 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 */);
|
||||
if (FLAGS_use_clock_cache) {
|
||||
cache_ = NewClockCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
if (!cache_) {
|
||||
fprintf(stderr, "Clock cache not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits, false, 0.5);
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
@@ -321,9 +247,6 @@ class CacheBench {
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
cache_ = NewLRUCache(opts);
|
||||
} else {
|
||||
fprintf(stderr, "Cache type not supported.");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,9 +256,8 @@ class CacheBench {
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
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());
|
||||
cache_->Insert(keygen.GetRand(rnd, max_key_, max_log_), createValue(rnd),
|
||||
&helper1, FLAGS_value_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,8 +361,6 @@ class CacheBench {
|
||||
uint64_t total_key_size = 0;
|
||||
uint64_t total_charge = 0;
|
||||
uint64_t total_entry_count = 0;
|
||||
uint64_t table_occupancy = 0;
|
||||
uint64_t table_size = 0;
|
||||
std::set<Cache::DeleterFn> deleters;
|
||||
StopWatchNano timer(clock);
|
||||
|
||||
@@ -456,15 +376,11 @@ class CacheBench {
|
||||
std::ostringstream ostr;
|
||||
ostr << "Most recent cache entry stats:\n"
|
||||
<< "Number of entries: " << total_entry_count << "\n"
|
||||
<< "Table occupancy: " << table_occupancy << " / "
|
||||
<< table_size << " = "
|
||||
<< (100.0 * table_occupancy / table_size) << "%\n"
|
||||
<< "Total charge: " << BytesToHumanString(total_charge) << "\n"
|
||||
<< "Average key size: "
|
||||
<< (1.0 * total_key_size / total_entry_count) << "\n"
|
||||
<< "Average charge: "
|
||||
<< BytesToHumanString(static_cast<uint64_t>(
|
||||
1.0 * total_charge / total_entry_count))
|
||||
<< BytesToHumanString(1.0 * total_charge / total_entry_count)
|
||||
<< "\n"
|
||||
<< "Unique deleters: " << deleters.size() << "\n";
|
||||
*stats_report = ostr.str();
|
||||
@@ -495,8 +411,6 @@ class CacheBench {
|
||||
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();
|
||||
stats_hist->Add(timer.ElapsedNanos() / 1000);
|
||||
}
|
||||
}
|
||||
@@ -536,19 +450,17 @@ class CacheBench {
|
||||
StopWatchNano timer(clock);
|
||||
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
timer.Start();
|
||||
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 {
|
||||
Cache::CreateCallback create_cb =
|
||||
[](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();
|
||||
};
|
||||
|
||||
timer.Start();
|
||||
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
@@ -558,16 +470,13 @@ class CacheBench {
|
||||
handle = cache_->Lookup(key, &helper2, create_cb, Cache::Priority::LOW,
|
||||
true);
|
||||
if (handle) {
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
} else {
|
||||
// do insert
|
||||
Status s = cache_->Insert(key, createValue(thread->rnd), &helper2,
|
||||
FLAGS_value_bytes, &handle);
|
||||
assert(s.ok());
|
||||
cache_->Insert(key, createValue(thread->rnd), &helper2,
|
||||
FLAGS_value_bytes, &handle);
|
||||
}
|
||||
} else if (random_op < insert_threshold_) {
|
||||
if (handle) {
|
||||
@@ -575,9 +484,8 @@ class CacheBench {
|
||||
handle = nullptr;
|
||||
}
|
||||
// do insert
|
||||
Status s = cache_->Insert(key, createValue(thread->rnd), &helper3,
|
||||
FLAGS_value_bytes, &handle);
|
||||
assert(s.ok());
|
||||
cache_->Insert(key, createValue(thread->rnd), &helper3,
|
||||
FLAGS_value_bytes, &handle);
|
||||
} else if (random_op < lookup_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
@@ -587,11 +495,9 @@ class CacheBench {
|
||||
handle = cache_->Lookup(key, &helper2, create_cb, Cache::Priority::LOW,
|
||||
true);
|
||||
if (handle) {
|
||||
if (!FLAGS_lean) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
@@ -615,15 +521,7 @@ class CacheBench {
|
||||
}
|
||||
|
||||
void PrintEnv() const {
|
||||
#if defined(__GNUC__) && !defined(__OPTIMIZE__)
|
||||
printf(
|
||||
"WARNING: Optimization is disabled: benchmarks unnecessarily slow\n");
|
||||
#endif
|
||||
#ifndef NDEBUG
|
||||
printf("WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
|
||||
#endif
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
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);
|
||||
printf("Cache size : %s\n",
|
||||
@@ -649,313 +547,9 @@ class CacheBench {
|
||||
}
|
||||
};
|
||||
|
||||
// cache_bench -stress_cache_key is an independent embedded tool for
|
||||
// estimating the probability of CacheKey collisions through simulation.
|
||||
// At a high level, it simulates generating SST files over many months,
|
||||
// keeping them in the DB and/or cache for some lifetime while staying
|
||||
// under resource caps, and checking for any cache key collisions that
|
||||
// arise among the set of live files. For efficient simulation, we make
|
||||
// some simplifying "pessimistic" assumptions (that only increase the
|
||||
// chance of the simulation reporting a collision relative to the chance
|
||||
// of collision in practice):
|
||||
// * Every generated file has a cache entry for every byte offset in the
|
||||
// file (contiguous range of cache keys)
|
||||
// * All of every file is cached for its entire lifetime. (Here "lifetime"
|
||||
// is technically the union of DB and Cache lifetime, though we only
|
||||
// model a generous DB lifetime, where space usage is always maximized.
|
||||
// In a effective Cache, lifetime in cache can only substantially exceed
|
||||
// lifetime in DB if there is little cache activity; cache activity is
|
||||
// required to hit cache key collisions.)
|
||||
//
|
||||
// It would be possible to track an exact set of cache key ranges for the
|
||||
// set of live files, but we would have no hope of observing collisions
|
||||
// (overlap in live files) in our simulation. We need to employ some way
|
||||
// of amplifying collision probability that allows us to predict the real
|
||||
// collision probability by extrapolation from observed collisions. Our
|
||||
// basic approach is to reduce each cache key range down to some smaller
|
||||
// number of bits, and limiting to bits that are shared over the whole
|
||||
// range. Now we can observe collisions using a set of smaller stripped-down
|
||||
// (reduced) cache keys. Let's do some case analysis to understand why this
|
||||
// works:
|
||||
// * No collision in reduced key - because the reduction is a pure function
|
||||
// this implies no collision in the full keys
|
||||
// * Collision detected between two reduced keys - either
|
||||
// * The reduction has dropped some structured uniqueness info (from one of
|
||||
// session counter or file number; file offsets are never materialized here).
|
||||
// This can only artificially inflate the observed and extrapolated collision
|
||||
// probabilities. We only have to worry about this in designing the reduction.
|
||||
// * The reduction has preserved all the structured uniqueness in the cache
|
||||
// key, which means either
|
||||
// * REJECTED: We have a uniqueness bug in generating cache keys, where
|
||||
// structured uniqueness info should have been different but isn't. In such a
|
||||
// case, increasing by 1 the number of bits kept after reduction would not
|
||||
// reduce observed probabilities by half. (In our observations, the
|
||||
// probabilities are reduced approximately by half.)
|
||||
// * ACCEPTED: The lost unstructured uniqueness in the key determines the
|
||||
// probability that an observed collision would imply an overlap in ranges.
|
||||
// In short, dropping n bits from key would increase collision probability by
|
||||
// 2**n, assuming those n bits have full entropy in unstructured uniqueness.
|
||||
//
|
||||
// But we also have to account for the key ranges based on file size. If file
|
||||
// sizes are roughly 2**b offsets, using XOR in 128-bit cache keys for
|
||||
// "ranges", we know from other simulations (see
|
||||
// https://github.com/pdillinger/unique_id/) that that's roughly equivalent to
|
||||
// (less than 2x higher collision probability) using a cache key of size
|
||||
// 128 - b bits for the whole file. (This is the only place we make an
|
||||
// "optimistic" assumption, which is more than offset by the real
|
||||
// implementation stripping off 2 lower bits from block byte offsets for cache
|
||||
// keys. The simulation assumes byte offsets, which is net pessimistic.)
|
||||
//
|
||||
// So to accept the extrapolation as valid, we need to be confident that all
|
||||
// "lost" bits, excluding those covered by file offset, are full entropy.
|
||||
// Recall that we have assumed (verifiably, safely) that other structured data
|
||||
// (file number and session counter) are kept, not lost. Based on the
|
||||
// implementation comments for OffsetableCacheKey, the only potential hole here
|
||||
// is that we only have ~103 bits of entropy in "all new" session IDs, and in
|
||||
// extreme cases, there might be only 1 DB ID. However, because the upper ~39
|
||||
// bits of session ID are hashed, the combination of file number and file
|
||||
// offset only has to add to 25 bits (or more) to ensure full entropy in
|
||||
// unstructured uniqueness lost in the reduction. Typical file size of 32MB
|
||||
// suffices (at least for simulation purposes where we assume each file offset
|
||||
// occupies a cache key).
|
||||
//
|
||||
// Example results in comments on OffsetableCacheKey.
|
||||
class StressCacheKey {
|
||||
public:
|
||||
void Run() {
|
||||
if (FLAGS_sck_footer_unique_id) {
|
||||
// Proposed footer unique IDs are DB-independent and session-independent
|
||||
// (but process-dependent) which is most easily simulated here by
|
||||
// assuming 1 DB and (later below) no session resets without process
|
||||
// reset.
|
||||
FLAGS_sck_db_count = 1;
|
||||
}
|
||||
|
||||
// Describe the simulated workload
|
||||
uint64_t mb_per_day =
|
||||
uint64_t{FLAGS_sck_files_per_day} * FLAGS_sck_file_size_mb;
|
||||
printf("Total cache or DBs size: %gTiB Writing %g MiB/s or %gTiB/day\n",
|
||||
FLAGS_sck_file_size_mb / 1024.0 / 1024.0 *
|
||||
std::pow(2.0, FLAGS_sck_table_bits),
|
||||
mb_per_day / 86400.0, mb_per_day / 1024.0 / 1024.0);
|
||||
// For extrapolating probability of any collisions from a number of
|
||||
// observed collisions
|
||||
multiplier_ = std::pow(2.0, 128 - FLAGS_sck_keep_bits) /
|
||||
(FLAGS_sck_file_size_mb * 1024.0 * 1024.0);
|
||||
printf(
|
||||
"Multiply by %g to correct for simulation losses (but still assume "
|
||||
"whole file cached)\n",
|
||||
multiplier_);
|
||||
restart_nfiles_ = FLAGS_sck_files_per_day / FLAGS_sck_restarts_per_day;
|
||||
double without_ejection =
|
||||
std::pow(1.414214, FLAGS_sck_keep_bits) / FLAGS_sck_files_per_day;
|
||||
// This should be a lower bound for -sck_randomize, usually a terribly
|
||||
// rough lower bound.
|
||||
// If observation is worse than this, then something has gone wrong.
|
||||
printf(
|
||||
"Without ejection, expect random collision after %g days (%g "
|
||||
"corrected)\n",
|
||||
without_ejection, without_ejection * multiplier_);
|
||||
double with_full_table =
|
||||
std::pow(2.0, FLAGS_sck_keep_bits - FLAGS_sck_table_bits) /
|
||||
FLAGS_sck_files_per_day;
|
||||
// This is an alternate lower bound for -sck_randomize, usually pretty
|
||||
// accurate. Our cache keys should usually perform "better than random"
|
||||
// but always no worse. (If observation is substantially worse than this,
|
||||
// then something has gone wrong.)
|
||||
printf(
|
||||
"With ejection and full table, expect random collision after %g "
|
||||
"days (%g corrected)\n",
|
||||
with_full_table, with_full_table * multiplier_);
|
||||
collisions_ = 0;
|
||||
|
||||
// Run until sufficient number of observed collisions.
|
||||
for (int i = 1; collisions_ < FLAGS_sck_min_collision; i++) {
|
||||
RunOnce();
|
||||
if (collisions_ == 0) {
|
||||
printf(
|
||||
"No collisions after %d x %u days "
|
||||
" \n",
|
||||
i, FLAGS_sck_days_per_run);
|
||||
} else {
|
||||
double est = 1.0 * i * FLAGS_sck_days_per_run / collisions_;
|
||||
printf("%" PRIu64
|
||||
" collisions after %d x %u days, est %g days between (%g "
|
||||
"corrected) \n",
|
||||
collisions_, i, FLAGS_sck_days_per_run, est, est * multiplier_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RunOnce() {
|
||||
// Re-initialized simulated state
|
||||
const size_t db_count = std::max(size_t{FLAGS_sck_db_count}, size_t{1});
|
||||
dbs_.reset(new TableProperties[db_count]{});
|
||||
const size_t table_mask = (size_t{1} << FLAGS_sck_table_bits) - 1;
|
||||
table_.reset(new uint64_t[table_mask + 1]{});
|
||||
if (FLAGS_sck_keep_bits > 64) {
|
||||
FLAGS_sck_keep_bits = 64;
|
||||
}
|
||||
|
||||
// Details of which bits are dropped in reduction
|
||||
uint32_t shift_away = 64 - FLAGS_sck_keep_bits;
|
||||
// Shift away fewer potential file number bits (b) than potential
|
||||
// session counter bits (a).
|
||||
uint32_t shift_away_b = shift_away / 3;
|
||||
uint32_t shift_away_a = shift_away - shift_away_b;
|
||||
|
||||
process_count_ = 0;
|
||||
session_count_ = 0;
|
||||
newdb_count_ = 0;
|
||||
ResetProcess(/*newdbs*/ true);
|
||||
|
||||
Random64 r{std::random_device{}()};
|
||||
|
||||
uint64_t max_file_count =
|
||||
uint64_t{FLAGS_sck_files_per_day} * FLAGS_sck_days_per_run;
|
||||
uint32_t report_count = 0;
|
||||
uint32_t collisions_this_run = 0;
|
||||
size_t db_i = 0;
|
||||
|
||||
for (uint64_t file_count = 1; file_count <= max_file_count;
|
||||
++file_count, ++db_i) {
|
||||
// Round-robin through DBs (this faster than %)
|
||||
if (db_i >= db_count) {
|
||||
db_i = 0;
|
||||
}
|
||||
// Any other periodic actions before simulating next file
|
||||
if (!FLAGS_sck_footer_unique_id && r.OneIn(FLAGS_sck_reopen_nfiles)) {
|
||||
ResetSession(db_i, /*newdb*/ r.OneIn(FLAGS_sck_newdb_nreopen));
|
||||
} else if (r.OneIn(restart_nfiles_)) {
|
||||
ResetProcess(/*newdbs*/ false);
|
||||
}
|
||||
// Simulate next file
|
||||
OffsetableCacheKey ock;
|
||||
dbs_[db_i].orig_file_number += 1;
|
||||
// skip some file numbers for other file kinds, except in footer unique
|
||||
// ID, orig_file_number here tracks process-wide generated SST file
|
||||
// count.
|
||||
if (!FLAGS_sck_footer_unique_id) {
|
||||
dbs_[db_i].orig_file_number += (r.Next() & 3);
|
||||
}
|
||||
bool is_stable;
|
||||
BlockBasedTable::SetupBaseCacheKey(&dbs_[db_i], /* ignored */ "",
|
||||
/* ignored */ 42, &ock, &is_stable);
|
||||
assert(is_stable);
|
||||
// Get a representative cache key, which later we analytically generalize
|
||||
// to a range.
|
||||
CacheKey ck = ock.WithOffset(0);
|
||||
uint64_t reduced_key;
|
||||
if (FLAGS_sck_randomize) {
|
||||
reduced_key = GetSliceHash64(ck.AsSlice()) >> shift_away;
|
||||
} else if (FLAGS_sck_footer_unique_id) {
|
||||
// Special case: keep only file number, not session counter
|
||||
reduced_key = DecodeFixed64(ck.AsSlice().data()) >> shift_away;
|
||||
} else {
|
||||
// Try to keep file number and session counter (shift away other bits)
|
||||
uint32_t a = DecodeFixed32(ck.AsSlice().data()) << shift_away_a;
|
||||
uint32_t b = DecodeFixed32(ck.AsSlice().data() + 4) >> shift_away_b;
|
||||
reduced_key = (uint64_t{a} << 32) + b;
|
||||
}
|
||||
if (reduced_key == 0) {
|
||||
// Unlikely, but we need to exclude tracking this value because we
|
||||
// use it to mean "empty" in table. This case is OK as long as we
|
||||
// don't hit it often.
|
||||
printf("Hit Zero! \n");
|
||||
file_count--;
|
||||
continue;
|
||||
}
|
||||
uint64_t h =
|
||||
NPHash64(reinterpret_cast<char*>(&reduced_key), sizeof(reduced_key));
|
||||
// Skew expected lifetimes, for high variance (super-Poisson) variance
|
||||
// in actual lifetimes.
|
||||
size_t pos =
|
||||
std::min(Lower32of64(h) & table_mask, Upper32of64(h) & table_mask);
|
||||
if (table_[pos] == reduced_key) {
|
||||
collisions_this_run++;
|
||||
// Our goal is to predict probability of no collisions, not expected
|
||||
// number of collisions. To make the distinction, we have to get rid
|
||||
// of observing correlated collisions, which this takes care of:
|
||||
ResetProcess(/*newdbs*/ false);
|
||||
} else {
|
||||
// Replace (end of lifetime for file that was in this slot)
|
||||
table_[pos] = reduced_key;
|
||||
}
|
||||
|
||||
if (++report_count == FLAGS_sck_files_per_day) {
|
||||
report_count = 0;
|
||||
// Estimate fill %
|
||||
size_t incr = table_mask / 1000;
|
||||
size_t sampled_count = 0;
|
||||
for (size_t i = 0; i <= table_mask; i += incr) {
|
||||
if (table_[i] != 0) {
|
||||
sampled_count++;
|
||||
}
|
||||
}
|
||||
// Report
|
||||
printf(
|
||||
"%" PRIu64 " days, %" PRIu64 " proc, %" PRIu64 " sess, %" PRIu64
|
||||
" newdb, %u coll, occ %g%%, ejected %g%% \r",
|
||||
file_count / FLAGS_sck_files_per_day, process_count_,
|
||||
session_count_, newdb_count_ - FLAGS_sck_db_count,
|
||||
collisions_this_run, 100.0 * sampled_count / 1000.0,
|
||||
100.0 * (1.0 - sampled_count / 1000.0 * table_mask / file_count));
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
collisions_ += collisions_this_run;
|
||||
}
|
||||
|
||||
void ResetSession(size_t i, bool newdb) {
|
||||
dbs_[i].db_session_id = DBImpl::GenerateDbSessionId(nullptr);
|
||||
if (newdb) {
|
||||
++newdb_count_;
|
||||
if (FLAGS_sck_footer_unique_id) {
|
||||
// Simulate how footer id would behave
|
||||
dbs_[i].db_id = "none";
|
||||
} else {
|
||||
// db_id might be ignored, depending on the implementation details
|
||||
dbs_[i].db_id = std::to_string(newdb_count_);
|
||||
dbs_[i].orig_file_number = 0;
|
||||
}
|
||||
}
|
||||
session_count_++;
|
||||
}
|
||||
|
||||
void ResetProcess(bool newdbs) {
|
||||
process_count_++;
|
||||
DBImpl::TEST_ResetDbSessionIdGen();
|
||||
for (size_t i = 0; i < FLAGS_sck_db_count; ++i) {
|
||||
ResetSession(i, newdbs);
|
||||
}
|
||||
if (FLAGS_sck_footer_unique_id) {
|
||||
// For footer unique ID, this tracks process-wide generated SST file
|
||||
// count.
|
||||
dbs_[0].orig_file_number = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// Use db_session_id and orig_file_number from TableProperties
|
||||
std::unique_ptr<TableProperties[]> dbs_;
|
||||
std::unique_ptr<uint64_t[]> table_;
|
||||
uint64_t process_count_ = 0;
|
||||
uint64_t session_count_ = 0;
|
||||
uint64_t newdb_count_ = 0;
|
||||
uint64_t collisions_ = 0;
|
||||
uint32_t restart_nfiles_ = 0;
|
||||
double multiplier_ = 0.0;
|
||||
};
|
||||
|
||||
int cache_bench_tool(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_stress_cache_key) {
|
||||
// Alternate tool
|
||||
StressCacheKey().Run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
|
||||
Vendored
+5
-71
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
std::array<std::string, kNumCacheEntryRoles> kCacheEntryRoleToCamelString{{
|
||||
std::array<const char*, kNumCacheEntryRoles> kCacheEntryRoleToCamelString{{
|
||||
"DataBlock",
|
||||
"FilterBlock",
|
||||
"FilterMetaBlock",
|
||||
@@ -20,15 +20,10 @@ std::array<std::string, kNumCacheEntryRoles> kCacheEntryRoleToCamelString{{
|
||||
"OtherBlock",
|
||||
"WriteBuffer",
|
||||
"CompressionDictionaryBuildingBuffer",
|
||||
"FilterConstruction",
|
||||
"BlockBasedTableReader",
|
||||
"FileMetadata",
|
||||
"BlobValue",
|
||||
"BlobCache",
|
||||
"Misc",
|
||||
}};
|
||||
|
||||
std::array<std::string, kNumCacheEntryRoles> kCacheEntryRoleToHyphenString{{
|
||||
std::array<const char*, kNumCacheEntryRoles> kCacheEntryRoleToHyphenString{{
|
||||
"data-block",
|
||||
"filter-block",
|
||||
"filter-meta-block",
|
||||
@@ -37,80 +32,19 @@ std::array<std::string, kNumCacheEntryRoles> kCacheEntryRoleToHyphenString{{
|
||||
"other-block",
|
||||
"write-buffer",
|
||||
"compression-dictionary-building-buffer",
|
||||
"filter-construction",
|
||||
"block-based-table-reader",
|
||||
"file-metadata",
|
||||
"blob-value",
|
||||
"blob-cache",
|
||||
"misc",
|
||||
}};
|
||||
|
||||
const std::string& GetCacheEntryRoleName(CacheEntryRole role) {
|
||||
return kCacheEntryRoleToHyphenString[static_cast<size_t>(role)];
|
||||
}
|
||||
|
||||
const std::string& BlockCacheEntryStatsMapKeys::CacheId() {
|
||||
static const std::string kCacheId = "id";
|
||||
return kCacheId;
|
||||
}
|
||||
|
||||
const std::string& BlockCacheEntryStatsMapKeys::CacheCapacityBytes() {
|
||||
static const std::string kCacheCapacityBytes = "capacity";
|
||||
return kCacheCapacityBytes;
|
||||
}
|
||||
|
||||
const std::string&
|
||||
BlockCacheEntryStatsMapKeys::LastCollectionDurationSeconds() {
|
||||
static const std::string kLastCollectionDurationSeconds =
|
||||
"secs_for_last_collection";
|
||||
return kLastCollectionDurationSeconds;
|
||||
}
|
||||
|
||||
const std::string& BlockCacheEntryStatsMapKeys::LastCollectionAgeSeconds() {
|
||||
static const std::string kLastCollectionAgeSeconds =
|
||||
"secs_since_last_collection";
|
||||
return kLastCollectionAgeSeconds;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
std::string GetPrefixedCacheEntryRoleName(const std::string& prefix,
|
||||
CacheEntryRole role) {
|
||||
const std::string& role_name = GetCacheEntryRoleName(role);
|
||||
std::string prefixed_role_name;
|
||||
prefixed_role_name.reserve(prefix.size() + role_name.size());
|
||||
prefixed_role_name.append(prefix);
|
||||
prefixed_role_name.append(role_name);
|
||||
return prefixed_role_name;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string BlockCacheEntryStatsMapKeys::EntryCount(CacheEntryRole role) {
|
||||
const static std::string kPrefix = "count.";
|
||||
return GetPrefixedCacheEntryRoleName(kPrefix, role);
|
||||
}
|
||||
|
||||
std::string BlockCacheEntryStatsMapKeys::UsedBytes(CacheEntryRole role) {
|
||||
const static std::string kPrefix = "bytes.";
|
||||
return GetPrefixedCacheEntryRoleName(kPrefix, role);
|
||||
}
|
||||
|
||||
std::string BlockCacheEntryStatsMapKeys::UsedPercent(CacheEntryRole role) {
|
||||
const static std::string kPrefix = "percent.";
|
||||
return GetPrefixedCacheEntryRoleName(kPrefix, role);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct Registry {
|
||||
std::mutex mutex;
|
||||
UnorderedMap<Cache::DeleterFn, CacheEntryRole> role_map;
|
||||
std::unordered_map<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::unordered_map<Cache::DeleterFn, CacheEntryRole> Copy() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return role_map;
|
||||
}
|
||||
@@ -127,7 +61,7 @@ void RegisterCacheDeleterRole(Cache::DeleterFn fn, CacheEntryRole role) {
|
||||
GetRegistry().Register(fn, role);
|
||||
}
|
||||
|
||||
UnorderedMap<Cache::DeleterFn, CacheEntryRole> CopyCacheDeleterRoleMap() {
|
||||
std::unordered_map<Cache::DeleterFn, CacheEntryRole> CopyCacheDeleterRoleMap() {
|
||||
return GetRegistry().Copy();
|
||||
}
|
||||
|
||||
|
||||
Vendored
+36
-12
@@ -7,17 +7,43 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "util/hash_containers.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
extern std::array<std::string, kNumCacheEntryRoles>
|
||||
// Classifications of block cache entries, for reporting statistics
|
||||
// Adding new enum to this class requires corresponding updates to
|
||||
// kCacheEntryRoleToCamelString and kCacheEntryRoleToHyphenString
|
||||
enum class CacheEntryRole {
|
||||
// Block-based table data block
|
||||
kDataBlock,
|
||||
// Block-based table filter block (full or partitioned)
|
||||
kFilterBlock,
|
||||
// Block-based table metadata block for partitioned filter
|
||||
kFilterMetaBlock,
|
||||
// Block-based table deprecated filter block (old "block-based" filter)
|
||||
kDeprecatedFilterBlock,
|
||||
// Block-based table index block
|
||||
kIndexBlock,
|
||||
// Other kinds of block-based table block
|
||||
kOtherBlock,
|
||||
// WriteBufferManager reservations to account for memtable usage
|
||||
kWriteBuffer,
|
||||
// BlockBasedTableBuilder reservations to account for
|
||||
// compression dictionary building buffer's memory usage
|
||||
kCompressionDictionaryBuildingBuffer,
|
||||
// Default bucket, for miscellaneous cache entries. Do not use for
|
||||
// entries that could potentially add up to large usage.
|
||||
kMisc,
|
||||
};
|
||||
constexpr uint32_t kNumCacheEntryRoles =
|
||||
static_cast<uint32_t>(CacheEntryRole::kMisc) + 1;
|
||||
|
||||
extern std::array<const char*, kNumCacheEntryRoles>
|
||||
kCacheEntryRoleToCamelString;
|
||||
extern std::array<std::string, kNumCacheEntryRoles>
|
||||
extern std::array<const char*, kNumCacheEntryRoles>
|
||||
kCacheEntryRoleToHyphenString;
|
||||
|
||||
// To associate cache entries with their role, we use a hack on the
|
||||
@@ -44,7 +70,7 @@ void RegisterCacheDeleterRole(Cache::DeleterFn fn, CacheEntryRole role);
|
||||
// * 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();
|
||||
std::unordered_map<Cache::DeleterFn, CacheEntryRole> CopyCacheDeleterRoleMap();
|
||||
|
||||
// ************************************************************** //
|
||||
// An automatic registration infrastructure. This enables code
|
||||
@@ -64,9 +90,7 @@ struct RegisteredDeleter {
|
||||
// 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));
|
||||
delete static_cast<T*>(value);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,9 +98,9 @@ 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.
|
||||
static void Delete(const Slice& /* key */, void* value) {
|
||||
(void)value;
|
||||
assert(value == nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Vendored
+7
-9
@@ -11,7 +11,6 @@
|
||||
#include <mutex>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "cache/cache_key.h"
|
||||
#include "port/lang.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -113,7 +112,13 @@ class CacheEntryStatsCollector {
|
||||
// entry in cache until all refs are destroyed.
|
||||
static Status GetShared(Cache *cache, SystemClock *clock,
|
||||
std::shared_ptr<CacheEntryStatsCollector> *ptr) {
|
||||
const Slice &cache_key = GetCacheKey();
|
||||
std::array<uint64_t, 3> cache_key_data{
|
||||
{// First 16 bytes == md5 of class name
|
||||
0x7eba5a8fb5437c90U, 0x8ca68c9b11655855U,
|
||||
// Last 8 bytes based on a function pointer to make unique for each
|
||||
// template instantiation
|
||||
reinterpret_cast<uint64_t>(&CacheEntryStatsCollector::GetShared)}};
|
||||
Slice cache_key = GetSlice(&cache_key_data);
|
||||
|
||||
Cache::Handle *h = cache->Lookup(cache_key);
|
||||
if (h == nullptr) {
|
||||
@@ -161,13 +166,6 @@ class CacheEntryStatsCollector {
|
||||
delete static_cast<CacheEntryStatsCollector *>(value);
|
||||
}
|
||||
|
||||
static const Slice &GetCacheKey() {
|
||||
// For each template instantiation
|
||||
static CacheKey ckey = CacheKey::CreateUniqueForProcessLifetime();
|
||||
static Slice ckey_slice = ckey.AsSlice();
|
||||
return ckey_slice;
|
||||
}
|
||||
|
||||
std::mutex saved_mutex_;
|
||||
Stats saved_stats_;
|
||||
|
||||
|
||||
Vendored
-20
@@ -84,16 +84,6 @@ class CacheHandleGuard {
|
||||
Cache::Handle* GetCacheHandle() const { return handle_; }
|
||||
T* GetValue() const { return value_; }
|
||||
|
||||
void TransferTo(Cleanable* cleanable) {
|
||||
if (cleanable) {
|
||||
if (handle_ != nullptr) {
|
||||
assert(cache_);
|
||||
cleanable->RegisterCleanup(&ReleaseCacheHandle, cache_, handle_);
|
||||
}
|
||||
}
|
||||
ResetFields();
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
ReleaseHandle();
|
||||
ResetFields();
|
||||
@@ -115,16 +105,6 @@ 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;
|
||||
|
||||
Vendored
-364
@@ -1,364 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. 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 "cache/cache_key.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Value space plan for CacheKey:
|
||||
//
|
||||
// file_num_etc64_ | offset_etc64_ | Only generated by
|
||||
// ---------------+---------------+------------------------------------------
|
||||
// 0 | 0 | Reserved for "empty" CacheKey()
|
||||
// 0 | > 0, < 1<<63 | CreateUniqueForCacheLifetime
|
||||
// 0 | >= 1<<63 | CreateUniqueForProcessLifetime
|
||||
// > 0 | any | OffsetableCacheKey.WithOffset
|
||||
|
||||
CacheKey CacheKey::CreateUniqueForCacheLifetime(Cache *cache) {
|
||||
// +1 so that we can reserve all zeros for "unset" cache key
|
||||
uint64_t id = cache->NewId() + 1;
|
||||
// Ensure we don't collide with CreateUniqueForProcessLifetime
|
||||
assert((id >> 63) == 0U);
|
||||
return CacheKey(0, id);
|
||||
}
|
||||
|
||||
CacheKey CacheKey::CreateUniqueForProcessLifetime() {
|
||||
// To avoid colliding with CreateUniqueForCacheLifetime, assuming
|
||||
// Cache::NewId counts up from zero, here we count down from UINT64_MAX.
|
||||
// If this ever becomes a point of contention, we could sub-divide the
|
||||
// space and use CoreLocalArray.
|
||||
static std::atomic<uint64_t> counter{UINT64_MAX};
|
||||
uint64_t id = counter.fetch_sub(1, std::memory_order_relaxed);
|
||||
// Ensure we don't collide with CreateUniqueForCacheLifetime
|
||||
assert((id >> 63) == 1U);
|
||||
return CacheKey(0, id);
|
||||
}
|
||||
|
||||
// How we generate CacheKeys and base OffsetableCacheKey, assuming that
|
||||
// db_session_ids are generated from a base_session_id and
|
||||
// session_id_counter (by SemiStructuredUniqueIdGen+EncodeSessionId
|
||||
// in DBImpl::GenerateDbSessionId):
|
||||
//
|
||||
// Conceptual inputs:
|
||||
// db_id (unstructured, from GenerateRawUniqueId or equiv)
|
||||
// * could be shared between cloned DBs but rare
|
||||
// * could be constant, if session id suffices
|
||||
// base_session_id (unstructured, from GenerateRawUniqueId)
|
||||
// session_id_counter (structured)
|
||||
// * usually much smaller than 2**24
|
||||
// orig_file_number (structured)
|
||||
// * usually smaller than 2**24
|
||||
// offset_in_file (structured, might skip lots of values)
|
||||
// * usually smaller than 2**32
|
||||
//
|
||||
// Overall approach (see https://github.com/pdillinger/unique_id for
|
||||
// background):
|
||||
//
|
||||
// First, we have three "structured" values, up to 64 bits each, that we
|
||||
// need to fit, without losses, into 128 bits. In practice, the values will
|
||||
// be small enough that they should fit. For example, applications generating
|
||||
// large SST files (large offsets) will naturally produce fewer files (small
|
||||
// file numbers). But we don't know ahead of time what bounds the values will
|
||||
// have.
|
||||
//
|
||||
// Second, we have unstructured inputs that enable distinct RocksDB processes
|
||||
// to pick a random point in space, likely very different from others. Xoring
|
||||
// the structured with the unstructured give us a cache key that is
|
||||
// structurally distinct between related keys (e.g. same file or same RocksDB
|
||||
// process) and distinct with high probability between unrelated keys.
|
||||
//
|
||||
// The problem of packing three structured values into the space for two is
|
||||
// complicated by the fact that we want to derive cache keys from SST unique
|
||||
// IDs, which have already combined structured and unstructured inputs in a
|
||||
// practically inseparable way. And we want a base cache key that works
|
||||
// with an offset of any size. So basically, we need to encode these three
|
||||
// structured values, each up to 64 bits, into 128 bits without knowing any
|
||||
// of their sizes. The DownwardInvolution() function gives us a mechanism to
|
||||
// accomplish this. (See its properties in math.h.) Specifically, for inputs
|
||||
// a, b, and c:
|
||||
// lower64 = DownwardInvolution(a) ^ ReverseBits(b);
|
||||
// upper64 = c ^ ReverseBits(a);
|
||||
// The 128-bit output is unique assuming there exist some i, j, and k
|
||||
// where a < 2**i, b < 2**j, c < 2**k, i <= 64, j <= 64, k <= 64, and
|
||||
// i + j + k <= 128. In other words, as long as there exist some bounds
|
||||
// that would allow us to pack the bits of a, b, and c into the output
|
||||
// if we know the bound, we can generate unique outputs without knowing
|
||||
// those bounds. To validate this claim, the inversion function (given
|
||||
// the bounds) has been implemented in CacheKeyDecoder in
|
||||
// db_block_cache_test.cc.
|
||||
//
|
||||
// With that in mind, the outputs in terms of the conceptual inputs look
|
||||
// like this, using bitwise-xor of the constituent pieces, low bits on left:
|
||||
//
|
||||
// |------------------------- file_num_etc64 -------------------------|
|
||||
// | +++++++++ base_session_id (lower 64 bits, involution) +++++++++ |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | session_id_counter (involution) ..... | |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | hash of: ++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
|
||||
// | * base_session_id (upper ~39 bits) |
|
||||
// | * db_id (~122 bits entropy) |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | | ..... orig_file_number (reversed) |
|
||||
// |-----------------------------------------------------------------|
|
||||
//
|
||||
//
|
||||
// |------------------------- offset_etc64 --------------------------|
|
||||
// | ++++++++++ base_session_id (lower 64 bits, reversed) ++++++++++ |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | | ..... session_id_counter (reversed) |
|
||||
// |-----------------------------------------------------------------|
|
||||
// | offset_in_file ............... | |
|
||||
// |-----------------------------------------------------------------|
|
||||
//
|
||||
// Some oddities or inconveniences of this layout are due to deriving
|
||||
// the "base" cache key (without offset) from the SST unique ID (see
|
||||
// GetSstInternalUniqueId). Specifically,
|
||||
// * Lower 64 of base_session_id occurs in both output words (ok but
|
||||
// weird)
|
||||
// * The inclusion of db_id is bad for the conditions under which we
|
||||
// can guarantee uniqueness, but could be useful in some cases with
|
||||
// few small files per process, to make up for db session id only having
|
||||
// ~103 bits of entropy.
|
||||
//
|
||||
// In fact, if DB ids were not involved, we would be guaranteed unique
|
||||
// cache keys for files generated in a single process until total bits for
|
||||
// biggest session_id_counter, orig_file_number, and offset_in_file
|
||||
// reach 128 bits.
|
||||
//
|
||||
// With the DB id limitation, we only have nice guaranteed unique cache
|
||||
// keys for files generated in a single process until biggest
|
||||
// session_id_counter and offset_in_file reach combined 64 bits. This
|
||||
// is quite good in practice because we can have millions of DB Opens
|
||||
// with terabyte size SST files, or billions of DB Opens with gigabyte
|
||||
// size SST files.
|
||||
//
|
||||
// One of the considerations in the translation between existing SST unique
|
||||
// IDs and base cache keys is supporting better SST unique IDs in a future
|
||||
// format_version. If we use a process-wide file counter instead of
|
||||
// session counter and file numbers, we only need to combine two 64-bit values
|
||||
// instead of three. But we don't want to track unique ID versions in the
|
||||
// manifest, so we want to keep the same translation layer between SST unique
|
||||
// IDs and base cache keys, even with updated SST unique IDs. If the new
|
||||
// unique IDs put the file counter where the orig_file_number was, and
|
||||
// use no structured field where session_id_counter was, then our translation
|
||||
// layer works fine for two structured fields as well as three (for
|
||||
// compatibility). The small computation for the translation (one
|
||||
// DownwardInvolution(), two ReverseBits(), both ~log(64) instructions deep)
|
||||
// is negligible for computing as part of SST file reader open.
|
||||
//
|
||||
// More on how https://github.com/pdillinger/unique_id applies here:
|
||||
// Every bit of output always includes "unstructured" uniqueness bits and
|
||||
// often combines with "structured" uniqueness bits. The "unstructured" bits
|
||||
// change infrequently: only when we cannot guarantee our state tracking for
|
||||
// "structured" uniqueness hasn't been cloned. Using a static
|
||||
// SemiStructuredUniqueIdGen for db_session_ids, this means we only get an
|
||||
// "all new" session id when a new process uses RocksDB. (Between processes,
|
||||
// we don't know if a DB or other persistent storage has been cloned. We
|
||||
// assume that if VM hot cloning is used, subsequently generated SST files
|
||||
// do not interact.) Within a process, only the session_lower of the
|
||||
// db_session_id changes incrementally ("structured" uniqueness).
|
||||
//
|
||||
// This basically means that our offsets, counters and file numbers allow us
|
||||
// to do somewhat "better than random" (birthday paradox) while in the
|
||||
// degenerate case of completely new session for each tiny file, we still
|
||||
// have strong uniqueness properties from the birthday paradox, with ~103
|
||||
// bit session IDs or up to 128 bits entropy with different DB IDs sharing a
|
||||
// cache.
|
||||
//
|
||||
// More collision probability analysis:
|
||||
// Suppose a RocksDB host generates (generously) 2 GB/s (10TB data, 17 DWPD)
|
||||
// with average process/session lifetime of (pessimistically) 4 minutes.
|
||||
// In 180 days (generous allowable data lifespan), we generate 31 million GB
|
||||
// of data, or 2^55 bytes, and 2^16 "all new" session IDs.
|
||||
//
|
||||
// First, suppose this is in a single DB (lifetime 180 days):
|
||||
// 128 bits cache key size
|
||||
// - 55 <- ideal size for byte offsets + file numbers
|
||||
// - 2 <- bits for offsets and file numbers not exactly powers of two
|
||||
// + 2 <- bits saved not using byte offsets in BlockBasedTable::GetCacheKey
|
||||
// ----
|
||||
// 73 <- bits remaining for distinguishing session IDs
|
||||
// The probability of a collision in 73 bits of session ID data is less than
|
||||
// 1 in 2**(73 - (2 * 16)), or roughly 1 in a trillion. And this assumes all
|
||||
// data from the last 180 days is in cache for potential collision, and that
|
||||
// cache keys under each session id exhaustively cover the remaining 57 bits
|
||||
// while in reality they'll only cover a small fraction of it.
|
||||
//
|
||||
// Although data could be transferred between hosts, each host has its own
|
||||
// cache and we are already assuming a high rate of "all new" session ids.
|
||||
// So this doesn't really change the collision calculation. Across a fleet
|
||||
// of 1 million, each with <1 in a trillion collision possibility,
|
||||
// fleetwide collision probability is <1 in a million.
|
||||
//
|
||||
// Now suppose we have many DBs per host, say 2**10, with same host-wide write
|
||||
// rate and process/session lifetime. File numbers will be ~10 bits smaller
|
||||
// and we will have 2**10 times as many session IDs because of simultaneous
|
||||
// lifetimes. So now collision chance is less than 1 in 2**(83 - (2 * 26)),
|
||||
// or roughly 1 in a billion.
|
||||
//
|
||||
// Suppose instead we generated random or hashed cache keys for each
|
||||
// (compressed) block. For 1KB compressed block size, that is 2^45 cache keys
|
||||
// in 180 days. Collision probability is more easily estimated at roughly
|
||||
// 1 in 2**(128 - (2 * 45)) or roughly 1 in a trillion (assuming all
|
||||
// data from the last 180 days is in cache, but NOT the other assumption
|
||||
// for the 1 in a trillion estimate above).
|
||||
//
|
||||
//
|
||||
// Collision probability estimation through simulation:
|
||||
// A tool ./cache_bench -stress_cache_key broadly simulates host-wide cache
|
||||
// activity over many months, by making some pessimistic simplifying
|
||||
// assumptions. See class StressCacheKey in cache_bench_tool.cc for details.
|
||||
// Here is some sample output with
|
||||
// `./cache_bench -stress_cache_key -sck_keep_bits=43`:
|
||||
//
|
||||
// Total cache or DBs size: 32TiB Writing 925.926 MiB/s or 76.2939TiB/day
|
||||
// Multiply by 1.15292e+18 to correct for simulation losses (but still
|
||||
// assume whole file cached)
|
||||
//
|
||||
// These come from default settings of 2.5M files per day of 32 MB each, and
|
||||
// `-sck_keep_bits=43` means that to represent a single file, we are only
|
||||
// keeping 43 bits of the 128-bit (base) cache key. With file size of 2**25
|
||||
// contiguous keys (pessimistic), our simulation is about 2\*\*(128-43-25) or
|
||||
// about 1 billion billion times more prone to collision than reality.
|
||||
//
|
||||
// More default assumptions, relatively pessimistic:
|
||||
// * 100 DBs in same process (doesn't matter much)
|
||||
// * Re-open DB in same process (new session ID related to old session ID) on
|
||||
// average every 100 files generated
|
||||
// * Restart process (all new session IDs unrelated to old) 24 times per day
|
||||
//
|
||||
// After enough data, we get a result at the end (-sck_keep_bits=43):
|
||||
//
|
||||
// (keep 43 bits) 18 collisions after 2 x 90 days, est 10 days between
|
||||
// (1.15292e+19 corrected)
|
||||
//
|
||||
// If we believe the (pessimistic) simulation and the mathematical
|
||||
// extrapolation, we would need to run a billion machines all for 11 billion
|
||||
// days to expect a cache key collision. To help verify that our extrapolation
|
||||
// ("corrected") is robust, we can make our simulation more precise by
|
||||
// increasing the "keep" bits, which takes more running time to get enough
|
||||
// collision data:
|
||||
//
|
||||
// (keep 44 bits) 16 collisions after 5 x 90 days, est 28.125 days between
|
||||
// (1.6213e+19 corrected)
|
||||
// (keep 45 bits) 15 collisions after 7 x 90 days, est 42 days between
|
||||
// (1.21057e+19 corrected)
|
||||
// (keep 46 bits) 15 collisions after 17 x 90 days, est 102 days between
|
||||
// (1.46997e+19 corrected)
|
||||
// (keep 47 bits) 15 collisions after 49 x 90 days, est 294 days between
|
||||
// (2.11849e+19 corrected)
|
||||
//
|
||||
// The extrapolated prediction seems to be within noise (sampling error).
|
||||
//
|
||||
// With the `-sck_randomize` option, we can see that typical workloads like
|
||||
// above have lower collision probability than "random" cache keys (note:
|
||||
// offsets still non-randomized) by a modest amount (roughly 2-3x less
|
||||
// collision prone than random), which should make us reasonably comfortable
|
||||
// even in "degenerate" cases (e.g. repeatedly launch a process to generate
|
||||
// one file with SstFileWriter):
|
||||
//
|
||||
// (rand 43 bits) 22 collisions after 1 x 90 days, est 4.09091 days between
|
||||
// (4.7165e+18 corrected)
|
||||
//
|
||||
// We can see that with more frequent process restarts,
|
||||
// -sck_restarts_per_day=5000, which means more all-new session IDs, we get
|
||||
// closer to the "random" cache key performance:
|
||||
//
|
||||
// 15 collisions after 1 x 90 days, est 6 days between (6.91753e+18 corrected)
|
||||
//
|
||||
// And with less frequent process restarts and re-opens,
|
||||
// -sck_restarts_per_day=1 -sck_reopen_nfiles=1000, we get lower collision
|
||||
// probability:
|
||||
//
|
||||
// 18 collisions after 8 x 90 days, est 40 days between (4.61169e+19 corrected)
|
||||
//
|
||||
// Other tests have been run to validate other conditions behave as expected,
|
||||
// never behaving "worse than random" unless we start chopping off structured
|
||||
// data.
|
||||
//
|
||||
// Conclusion: Even in extreme cases, rapidly burning through "all new" IDs
|
||||
// that only arise when a new process is started, the chance of any cache key
|
||||
// collisions in a giant fleet of machines is negligible. Especially when
|
||||
// processes live for hours or days, the chance of a cache key collision is
|
||||
// likely more plausibly due to bad hardware than to bad luck in random
|
||||
// session ID data. Software defects are surely more likely to cause corruption
|
||||
// than both of those.
|
||||
//
|
||||
// TODO: Nevertheless / regardless, an efficient way to detect (and thus
|
||||
// quantify) block cache corruptions, including collisions, should be added.
|
||||
OffsetableCacheKey::OffsetableCacheKey(const std::string &db_id,
|
||||
const std::string &db_session_id,
|
||||
uint64_t file_number) {
|
||||
UniqueId64x2 internal_id;
|
||||
Status s = GetSstInternalUniqueId(db_id, db_session_id, file_number,
|
||||
&internal_id, /*force=*/true);
|
||||
assert(s.ok());
|
||||
*this = FromInternalUniqueId(&internal_id);
|
||||
}
|
||||
|
||||
OffsetableCacheKey OffsetableCacheKey::FromInternalUniqueId(UniqueIdPtr id) {
|
||||
uint64_t session_lower = id.ptr[0];
|
||||
uint64_t file_num_etc = id.ptr[1];
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool is_empty = session_lower == 0 && file_num_etc == 0;
|
||||
#endif
|
||||
|
||||
// Although DBImpl guarantees (in recent versions) that session_lower is not
|
||||
// zero, that's not entirely sufficient to guarantee that file_num_etc64_ is
|
||||
// not zero (so that the 0 case can be used by CacheKey::CreateUnique*)
|
||||
// However, if we are given an "empty" id as input, then we should produce
|
||||
// "empty" as output.
|
||||
// As a consequence, this function is only bijective assuming
|
||||
// id[0] == 0 only if id[1] == 0.
|
||||
if (session_lower == 0U) {
|
||||
session_lower = file_num_etc;
|
||||
}
|
||||
|
||||
// See comments above for how DownwardInvolution and ReverseBits
|
||||
// make this function invertible under various assumptions.
|
||||
OffsetableCacheKey rv;
|
||||
rv.file_num_etc64_ =
|
||||
DownwardInvolution(session_lower) ^ ReverseBits(file_num_etc);
|
||||
rv.offset_etc64_ = ReverseBits(session_lower);
|
||||
|
||||
// Because of these transformations and needing to allow arbitrary
|
||||
// offset (thus, second 64 bits of cache key might be 0), we need to
|
||||
// make some correction to ensure the first 64 bits is not 0.
|
||||
// Fortunately, the transformation ensures the second 64 bits is not 0
|
||||
// for non-empty base key, so we can swap in the case one is 0 without
|
||||
// breaking bijectivity (assuming condition above).
|
||||
assert(is_empty || rv.offset_etc64_ > 0);
|
||||
if (rv.file_num_etc64_ == 0) {
|
||||
std::swap(rv.file_num_etc64_, rv.offset_etc64_);
|
||||
}
|
||||
assert(is_empty || rv.file_num_etc64_ > 0);
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Inverse of FromInternalUniqueId (assuming file_num_etc64 == 0 only if
|
||||
// offset_etc64 == 0)
|
||||
UniqueId64x2 OffsetableCacheKey::ToInternalUniqueId() {
|
||||
uint64_t a = file_num_etc64_;
|
||||
uint64_t b = offset_etc64_;
|
||||
if (b == 0) {
|
||||
std::swap(a, b);
|
||||
}
|
||||
UniqueId64x2 rv;
|
||||
rv[0] = ReverseBits(b);
|
||||
rv[1] = ReverseBits(a ^ DownwardInvolution(rv[0]));
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
-143
@@ -1,143 +0,0 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. 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).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "table/unique_id_impl.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Cache;
|
||||
|
||||
// A standard holder for fixed-size block cache keys (and for related caches).
|
||||
// They are created through one of these, each using its own range of values:
|
||||
// * CacheKey::CreateUniqueForCacheLifetime
|
||||
// * CacheKey::CreateUniqueForProcessLifetime
|
||||
// * Default ctor ("empty" cache key)
|
||||
// * OffsetableCacheKey->WithOffset
|
||||
//
|
||||
// The first two use atomic counters to guarantee uniqueness over the given
|
||||
// lifetime and the last uses a form of universally unique identifier for
|
||||
// uniqueness with very high probabilty (and guaranteed for files generated
|
||||
// during a single process lifetime).
|
||||
//
|
||||
// CacheKeys are currently used by calling AsSlice() to pass as a key to
|
||||
// Cache. For performance, the keys are endianness-dependent (though otherwise
|
||||
// portable). (Persistable cache entries are not intended to cross platforms.)
|
||||
class CacheKey {
|
||||
public:
|
||||
// For convenience, constructs an "empty" cache key that is never returned
|
||||
// by other means.
|
||||
inline CacheKey() : file_num_etc64_(), offset_etc64_() {}
|
||||
|
||||
inline bool IsEmpty() const {
|
||||
return (file_num_etc64_ == 0) & (offset_etc64_ == 0);
|
||||
}
|
||||
|
||||
// Use this cache key as a Slice (byte order is endianness-dependent)
|
||||
inline Slice AsSlice() const {
|
||||
static_assert(sizeof(*this) == 16, "Standardized on 16-byte cache key");
|
||||
assert(!IsEmpty());
|
||||
return Slice(reinterpret_cast<const char *>(this), sizeof(*this));
|
||||
}
|
||||
|
||||
// Create a CacheKey that is unique among others associated with this Cache
|
||||
// instance. Depends on Cache::NewId. This is useful for block cache
|
||||
// "reservations".
|
||||
static CacheKey CreateUniqueForCacheLifetime(Cache *cache);
|
||||
|
||||
// Create a CacheKey that is unique among others for the lifetime of this
|
||||
// process. This is useful for saving in a static data member so that
|
||||
// different DB instances can agree on a cache key for shared entities,
|
||||
// such as for CacheEntryStatsCollector.
|
||||
static CacheKey CreateUniqueForProcessLifetime();
|
||||
|
||||
protected:
|
||||
friend class OffsetableCacheKey;
|
||||
CacheKey(uint64_t file_num_etc64, uint64_t offset_etc64)
|
||||
: file_num_etc64_(file_num_etc64), offset_etc64_(offset_etc64) {}
|
||||
uint64_t file_num_etc64_;
|
||||
uint64_t offset_etc64_;
|
||||
};
|
||||
|
||||
constexpr uint8_t kCacheKeySize = static_cast<uint8_t>(sizeof(CacheKey));
|
||||
|
||||
// A file-specific generator of cache keys, sometimes referred to as the
|
||||
// "base" cache key for a file because all the cache keys for various offsets
|
||||
// within the file are computed using simple arithmetic. The basis for the
|
||||
// general approach is dicussed here: https://github.com/pdillinger/unique_id
|
||||
// Heavily related to GetUniqueIdFromTableProperties.
|
||||
//
|
||||
// If the db_id, db_session_id, and file_number come from the file's table
|
||||
// properties, then the keys will be stable across DB::Open/Close, backup/
|
||||
// restore, import/export, etc.
|
||||
//
|
||||
// This class "is a" CacheKey only privately so that it is not misused as
|
||||
// a ready-to-use CacheKey.
|
||||
class OffsetableCacheKey : private CacheKey {
|
||||
public:
|
||||
// For convenience, constructs an "empty" cache key that should not be used.
|
||||
inline OffsetableCacheKey() : CacheKey() {}
|
||||
|
||||
// Constructs an OffsetableCacheKey with the given information about a file.
|
||||
// This constructor never generates an "empty" base key.
|
||||
OffsetableCacheKey(const std::string &db_id, const std::string &db_session_id,
|
||||
uint64_t file_number);
|
||||
|
||||
// Creates an OffsetableCacheKey from an SST unique ID, so that cache keys
|
||||
// can be derived from DB manifest data before reading the file from
|
||||
// storage--so that every part of the file can potentially go in a persistent
|
||||
// cache.
|
||||
//
|
||||
// Calling GetSstInternalUniqueId() on a db_id, db_session_id, and
|
||||
// file_number and passing the result to this function produces the same
|
||||
// base cache key as feeding those inputs directly to the constructor.
|
||||
//
|
||||
// This is a bijective transformation assuming either id is empty or
|
||||
// lower 64 bits is non-zero:
|
||||
// * Empty (all zeros) input -> empty (all zeros) output
|
||||
// * Lower 64 input is non-zero -> lower 64 output (file_num_etc64_) is
|
||||
// non-zero
|
||||
static OffsetableCacheKey FromInternalUniqueId(UniqueIdPtr id);
|
||||
|
||||
// This is the inverse transformation to the above, assuming either empty
|
||||
// or lower 64 bits (file_num_etc64_) is non-zero. Perhaps only useful for
|
||||
// testing.
|
||||
UniqueId64x2 ToInternalUniqueId();
|
||||
|
||||
inline bool IsEmpty() const {
|
||||
bool result = file_num_etc64_ == 0;
|
||||
assert(!(offset_etc64_ > 0 && result));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Construct a CacheKey for an offset within a file. An offset is not
|
||||
// necessarily a byte offset if a smaller unique identifier of keyable
|
||||
// offsets is used.
|
||||
//
|
||||
// This class was designed to make this hot code extremely fast.
|
||||
inline CacheKey WithOffset(uint64_t offset) const {
|
||||
assert(!IsEmpty());
|
||||
return CacheKey(file_num_etc64_, offset_etc64_ ^ offset);
|
||||
}
|
||||
|
||||
// The "common prefix" is a shared prefix for all the returned CacheKeys.
|
||||
// It is specific to the file but the same for all offsets within the file.
|
||||
static constexpr size_t kCommonPrefixSize = 8;
|
||||
inline Slice CommonPrefixSlice() const {
|
||||
static_assert(sizeof(file_num_etc64_) == kCommonPrefixSize,
|
||||
"8 byte common prefix expected");
|
||||
assert(!IsEmpty());
|
||||
assert(&this->file_num_etc64_ == static_cast<const void *>(this));
|
||||
|
||||
return Slice(reinterpret_cast<const char *>(this), kCommonPrefixSize);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+28
-82
@@ -17,54 +17,34 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/block_based/reader_common.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
template <CacheEntryRole R>
|
||||
CacheReservationManagerImpl<R>::CacheReservationHandle::CacheReservationHandle(
|
||||
std::size_t incremental_memory_used,
|
||||
std::shared_ptr<CacheReservationManagerImpl> cache_res_mgr)
|
||||
: incremental_memory_used_(incremental_memory_used) {
|
||||
assert(cache_res_mgr);
|
||||
cache_res_mgr_ = cache_res_mgr;
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
CacheReservationManagerImpl<
|
||||
R>::CacheReservationHandle::~CacheReservationHandle() {
|
||||
Status s = cache_res_mgr_->ReleaseCacheReservation(incremental_memory_used_);
|
||||
s.PermitUncheckedError();
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
CacheReservationManagerImpl<R>::CacheReservationManagerImpl(
|
||||
std::shared_ptr<Cache> cache, bool delayed_decrease)
|
||||
: delayed_decrease_(delayed_decrease),
|
||||
cache_allocated_size_(0),
|
||||
memory_used_(0) {
|
||||
CacheReservationManager::CacheReservationManager(std::shared_ptr<Cache> cache,
|
||||
bool delayed_decrease)
|
||||
: delayed_decrease_(delayed_decrease), cache_allocated_size_(0) {
|
||||
assert(cache != nullptr);
|
||||
cache_ = cache;
|
||||
std::memset(cache_key_, 0, kCacheKeyPrefixSize + kMaxVarint64Length);
|
||||
EncodeVarint64(cache_key_, cache_->NewId());
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
CacheReservationManagerImpl<R>::~CacheReservationManagerImpl() {
|
||||
CacheReservationManager::~CacheReservationManager() {
|
||||
for (auto* handle : dummy_handles_) {
|
||||
cache_->Release(handle, true);
|
||||
}
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManagerImpl<R>::UpdateCacheReservation(
|
||||
Status CacheReservationManager::UpdateCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
memory_used_ = new_mem_used;
|
||||
std::size_t cur_cache_allocated_size =
|
||||
cache_allocated_size_.load(std::memory_order_relaxed);
|
||||
if (new_mem_used == cur_cache_allocated_size) {
|
||||
return Status::OK();
|
||||
} else if (new_mem_used > cur_cache_allocated_size) {
|
||||
Status s = IncreaseCacheReservation(new_mem_used);
|
||||
Status s = IncreaseCacheReservation<R>(new_mem_used);
|
||||
return s;
|
||||
} else {
|
||||
// In delayed decrease mode, we don't decrease cache reservation
|
||||
@@ -85,32 +65,19 @@ Status CacheReservationManagerImpl<R>::UpdateCacheReservation(
|
||||
}
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManagerImpl<R>::MakeCacheReservation(
|
||||
std::size_t incremental_memory_used,
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle>* handle) {
|
||||
assert(handle);
|
||||
Status s =
|
||||
UpdateCacheReservation(GetTotalMemoryUsed() + incremental_memory_used);
|
||||
(*handle).reset(new CacheReservationManagerImpl::CacheReservationHandle(
|
||||
incremental_memory_used,
|
||||
std::enable_shared_from_this<
|
||||
CacheReservationManagerImpl<R>>::shared_from_this()));
|
||||
return s;
|
||||
}
|
||||
// Explicitly instantiate templates for "CacheEntryRole" values we use.
|
||||
// This makes it possible to keep the template definitions in the .cc file.
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kWriteBuffer>(std::size_t new_mem_used);
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kCompressionDictionaryBuildingBuffer>(
|
||||
std::size_t new_mem_used);
|
||||
// For cache reservation manager unit tests
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kMisc>(std::size_t new_mem_used);
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManagerImpl<R>::ReleaseCacheReservation(
|
||||
std::size_t incremental_memory_used) {
|
||||
assert(GetTotalMemoryUsed() >= incremental_memory_used);
|
||||
std::size_t updated_total_mem_used =
|
||||
GetTotalMemoryUsed() - incremental_memory_used;
|
||||
Status s = UpdateCacheReservation(updated_total_mem_used);
|
||||
return s;
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManagerImpl<R>::IncreaseCacheReservation(
|
||||
Status CacheReservationManager::IncreaseCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
Status return_status = Status::OK();
|
||||
while (new_mem_used > cache_allocated_size_.load(std::memory_order_relaxed)) {
|
||||
@@ -128,8 +95,7 @@ Status CacheReservationManagerImpl<R>::IncreaseCacheReservation(
|
||||
return return_status;
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManagerImpl<R>::DecreaseCacheReservation(
|
||||
Status CacheReservationManager::DecreaseCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
Status return_status = Status::OK();
|
||||
|
||||
@@ -148,38 +114,18 @@ Status CacheReservationManagerImpl<R>::DecreaseCacheReservation(
|
||||
return return_status;
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
std::size_t CacheReservationManagerImpl<R>::GetTotalReservedCacheSize() {
|
||||
std::size_t CacheReservationManager::GetTotalReservedCacheSize() {
|
||||
return cache_allocated_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
std::size_t CacheReservationManagerImpl<R>::GetTotalMemoryUsed() {
|
||||
return memory_used_;
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Slice CacheReservationManagerImpl<R>::GetNextCacheKey() {
|
||||
Slice CacheReservationManager::GetNextCacheKey() {
|
||||
// Calling this function will have the side-effect of changing the
|
||||
// underlying cache_key_ that is shared among other keys generated from this
|
||||
// fucntion. Therefore please make sure the previous keys are saved/copied
|
||||
// before calling this function.
|
||||
cache_key_ = CacheKey::CreateUniqueForCacheLifetime(cache_.get());
|
||||
return cache_key_.AsSlice();
|
||||
std::memset(cache_key_ + kCacheKeyPrefixSize, 0, kMaxVarint64Length);
|
||||
char* end =
|
||||
EncodeVarint64(cache_key_ + kCacheKeyPrefixSize, next_cache_key_id_++);
|
||||
return Slice(cache_key_, static_cast<std::size_t>(end - cache_key_));
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Cache::DeleterFn CacheReservationManagerImpl<R>::TEST_GetNoopDeleterForRole() {
|
||||
return GetNoopDeleterForRole<R>();
|
||||
}
|
||||
|
||||
template class CacheReservationManagerImpl<
|
||||
CacheEntryRole::kBlockBasedTableReader>;
|
||||
template class CacheReservationManagerImpl<
|
||||
CacheEntryRole::kCompressionDictionaryBuildingBuffer>;
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kFilterConstruction>;
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kMisc>;
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kWriteBuffer>;
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kFileMetadata>;
|
||||
template class CacheReservationManagerImpl<CacheEntryRole::kBlobCache>;
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+32
-250
@@ -13,100 +13,48 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "cache/cache_key.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
// CacheReservationManager is an interface for reserving cache space for the
|
||||
// memory used
|
||||
|
||||
// CacheReservationManager is for reserving cache space for the memory used
|
||||
// through inserting/releasing dummy entries in the cache.
|
||||
// This class is not thread-safe.
|
||||
class CacheReservationManager {
|
||||
public:
|
||||
// CacheReservationHandle is for managing the lifetime of a cache reservation
|
||||
// for an incremental amount of memory used (i.e, incremental_memory_used)
|
||||
class CacheReservationHandle {
|
||||
public:
|
||||
virtual ~CacheReservationHandle() {}
|
||||
};
|
||||
virtual ~CacheReservationManager() {}
|
||||
virtual Status UpdateCacheReservation(std::size_t new_memory_used) = 0;
|
||||
// TODO(hx235): replace the usage of
|
||||
// `UpdateCacheReservation(memory_used_delta, increase)` with
|
||||
// `UpdateCacheReservation(new_memory_used)` so that we only have one
|
||||
// `UpdateCacheReservation` function
|
||||
virtual Status UpdateCacheReservation(std::size_t memory_used_delta,
|
||||
bool increase) = 0;
|
||||
virtual Status MakeCacheReservation(
|
||||
std::size_t incremental_memory_used,
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle>
|
||||
*handle) = 0;
|
||||
virtual std::size_t GetTotalReservedCacheSize() = 0;
|
||||
virtual std::size_t GetTotalMemoryUsed() = 0;
|
||||
};
|
||||
|
||||
// CacheReservationManagerImpl implements interface CacheReservationManager
|
||||
// for reserving cache space for the memory used by inserting/releasing dummy
|
||||
// entries in the cache.
|
||||
//
|
||||
// This class is NOT thread-safe, except that GetTotalReservedCacheSize()
|
||||
// can be called without external synchronization.
|
||||
template <CacheEntryRole R>
|
||||
class CacheReservationManagerImpl
|
||||
: public CacheReservationManager,
|
||||
public std::enable_shared_from_this<CacheReservationManagerImpl<R>> {
|
||||
public:
|
||||
class CacheReservationHandle
|
||||
: public CacheReservationManager::CacheReservationHandle {
|
||||
public:
|
||||
CacheReservationHandle(
|
||||
std::size_t incremental_memory_used,
|
||||
std::shared_ptr<CacheReservationManagerImpl> cache_res_mgr);
|
||||
~CacheReservationHandle() override;
|
||||
|
||||
private:
|
||||
std::size_t incremental_memory_used_;
|
||||
std::shared_ptr<CacheReservationManagerImpl> cache_res_mgr_;
|
||||
};
|
||||
|
||||
// Construct a CacheReservationManagerImpl
|
||||
// Construct a CacheReservationManager
|
||||
// @param cache The cache where dummy entries are inserted and released for
|
||||
// reserving cache space
|
||||
// @param delayed_decrease If set true, then dummy entries won't be released
|
||||
// immediately when memory usage decreases.
|
||||
// immediately when memory usage decreases.
|
||||
// Instead, it will be released when the memory usage
|
||||
// decreases to 3/4 of what we have reserved so far.
|
||||
// This is for saving some future dummy entry
|
||||
// insertion when memory usage increases are likely to
|
||||
// happen in the near future.
|
||||
//
|
||||
// REQUIRED: cache is not nullptr
|
||||
explicit CacheReservationManagerImpl(std::shared_ptr<Cache> cache,
|
||||
bool delayed_decrease = false);
|
||||
explicit CacheReservationManager(std::shared_ptr<Cache> cache,
|
||||
bool delayed_decrease = false);
|
||||
|
||||
// no copy constructor, copy assignment, move constructor, move assignment
|
||||
CacheReservationManagerImpl(const CacheReservationManagerImpl &) = delete;
|
||||
CacheReservationManagerImpl &operator=(const CacheReservationManagerImpl &) =
|
||||
delete;
|
||||
CacheReservationManagerImpl(CacheReservationManagerImpl &&) = delete;
|
||||
CacheReservationManagerImpl &operator=(CacheReservationManagerImpl &&) =
|
||||
delete;
|
||||
CacheReservationManager(const CacheReservationManager &) = delete;
|
||||
CacheReservationManager &operator=(const CacheReservationManager &) = delete;
|
||||
CacheReservationManager(CacheReservationManager &&) = delete;
|
||||
CacheReservationManager &operator=(CacheReservationManager &&) = delete;
|
||||
|
||||
~CacheReservationManagerImpl() override;
|
||||
~CacheReservationManager();
|
||||
|
||||
template <CacheEntryRole R>
|
||||
|
||||
// One of the two ways of reserving/releasing cache space,
|
||||
// see MakeCacheReservation() for the other.
|
||||
//
|
||||
// Use ONLY one of these two ways to prevent unexpected behavior.
|
||||
//
|
||||
// Insert and release dummy entries in the cache to
|
||||
// match the size of total dummy entries with the least multiple of
|
||||
// kSizeDummyEntry greater than or equal to new_mem_used
|
||||
// match the size of total dummy entries with the smallest multiple of
|
||||
// kSizeDummyEntry that is greater than or equal to new_mem_used
|
||||
//
|
||||
// Insert dummy entries if new_memory_used > cache_allocated_size_;
|
||||
//
|
||||
@@ -120,197 +68,31 @@ class CacheReservationManagerImpl
|
||||
// is set true.
|
||||
//
|
||||
// @param new_memory_used The number of bytes used by new memory
|
||||
// The most recent new_memoy_used passed in will be returned
|
||||
// in GetTotalMemoryUsed() even when the call return non-ok status.
|
||||
//
|
||||
// Since the class is NOT thread-safe, external synchronization on the
|
||||
// order of calling UpdateCacheReservation() is needed if you want
|
||||
// GetTotalMemoryUsed() indeed returns the latest memory used.
|
||||
//
|
||||
// @return On inserting dummy entries, it returns Status::OK() if all dummy
|
||||
// entry insertions succeed.
|
||||
// Otherwise, it returns the first non-ok status;
|
||||
// On releasing dummy entries, it always returns Status::OK().
|
||||
// On keeping dummy entries the same, it always returns Status::OK().
|
||||
Status UpdateCacheReservation(std::size_t new_memory_used) override;
|
||||
|
||||
Status UpdateCacheReservation(std::size_t /* memory_used_delta */,
|
||||
bool /* increase */) override {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
// One of the two ways of reserving cache space and releasing is done through
|
||||
// destruction of CacheReservationHandle.
|
||||
// See UpdateCacheReservation() for the other way.
|
||||
//
|
||||
// Use ONLY one of these two ways to prevent unexpected behavior.
|
||||
//
|
||||
// Insert dummy entries in the cache for the incremental memory usage
|
||||
// to match the size of total dummy entries with the least multiple of
|
||||
// kSizeDummyEntry greater than or equal to the total memory used.
|
||||
//
|
||||
// A CacheReservationHandle is returned as an output parameter.
|
||||
// The reserved dummy entries are automatically released on the destruction of
|
||||
// this handle, which achieves better RAII per cache reservation.
|
||||
//
|
||||
// WARNING: Deallocate all the handles of the CacheReservationManager object
|
||||
// before deallocating the object to prevent unexpected behavior.
|
||||
//
|
||||
// @param incremental_memory_used The number of bytes increased in memory
|
||||
// usage.
|
||||
//
|
||||
// Calling GetTotalMemoryUsed() afterward will return the total memory
|
||||
// increased by this number, even when calling MakeCacheReservation()
|
||||
// returns non-ok status.
|
||||
//
|
||||
// Since the class is NOT thread-safe, external synchronization in
|
||||
// calling MakeCacheReservation() is needed if you want
|
||||
// GetTotalMemoryUsed() indeed returns the latest memory used.
|
||||
//
|
||||
// @param handle An pointer to std::unique_ptr<CacheReservationHandle> that
|
||||
// manages the lifetime of the cache reservation represented by the
|
||||
// handle.
|
||||
//
|
||||
// @return It returns Status::OK() if all dummy
|
||||
// entry insertions succeed.
|
||||
// Otherwise, it returns the first non-ok status;
|
||||
//
|
||||
// REQUIRES: handle != nullptr
|
||||
Status MakeCacheReservation(
|
||||
std::size_t incremental_memory_used,
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle)
|
||||
override;
|
||||
|
||||
// Return the size of the cache (which is a multiple of kSizeDummyEntry)
|
||||
// successfully reserved by calling UpdateCacheReservation().
|
||||
//
|
||||
// When UpdateCacheReservation() returns non-ok status,
|
||||
// calling GetTotalReservedCacheSize() after that might return a slightly
|
||||
// smaller number than the actual reserved cache size due to
|
||||
// the returned number will always be a multiple of kSizeDummyEntry
|
||||
// and cache full might happen in the middle of inserting a dummy entry.
|
||||
std::size_t GetTotalReservedCacheSize() override;
|
||||
|
||||
// Return the latest total memory used indicated by the most recent call of
|
||||
// UpdateCacheReservation(std::size_t new_memory_used);
|
||||
std::size_t GetTotalMemoryUsed() override;
|
||||
|
||||
static constexpr std::size_t GetDummyEntrySize() { return kSizeDummyEntry; }
|
||||
|
||||
// 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 Cache::DeleterFn TEST_GetNoopDeleterForRole();
|
||||
// entry insertions succeed. Otherwise, it returns the first non-ok status;
|
||||
// On releasing dummy entries, it always returns Status::OK().
|
||||
// On keeping dummy entries the same, it always returns Status::OK().
|
||||
Status UpdateCacheReservation(std::size_t new_memory_used);
|
||||
std::size_t GetTotalReservedCacheSize();
|
||||
|
||||
private:
|
||||
static constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
// The key will be longer than keys for blocks in SST files so they won't
|
||||
// conflict.
|
||||
static const std::size_t kCacheKeyPrefixSize =
|
||||
BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length;
|
||||
|
||||
Slice GetNextCacheKey();
|
||||
|
||||
Status ReleaseCacheReservation(std::size_t incremental_memory_used);
|
||||
template <CacheEntryRole R>
|
||||
Status IncreaseCacheReservation(std::size_t new_mem_used);
|
||||
Status DecreaseCacheReservation(std::size_t new_mem_used);
|
||||
|
||||
std::shared_ptr<Cache> cache_;
|
||||
bool delayed_decrease_;
|
||||
std::atomic<std::size_t> cache_allocated_size_;
|
||||
std::size_t memory_used_;
|
||||
std::vector<Cache::Handle *> dummy_handles_;
|
||||
CacheKey cache_key_;
|
||||
std::uint64_t next_cache_key_id_ = 0;
|
||||
// The non-prefix part will be updated according to the ID to use.
|
||||
char cache_key_[kCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
};
|
||||
|
||||
class ConcurrentCacheReservationManager
|
||||
: public CacheReservationManager,
|
||||
public std::enable_shared_from_this<ConcurrentCacheReservationManager> {
|
||||
public:
|
||||
class CacheReservationHandle
|
||||
: public CacheReservationManager::CacheReservationHandle {
|
||||
public:
|
||||
CacheReservationHandle(
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr,
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle>
|
||||
cache_res_handle) {
|
||||
assert(cache_res_mgr && cache_res_handle);
|
||||
cache_res_mgr_ = cache_res_mgr;
|
||||
cache_res_handle_ = std::move(cache_res_handle);
|
||||
}
|
||||
|
||||
~CacheReservationHandle() override {
|
||||
std::lock_guard<std::mutex> lock(cache_res_mgr_->cache_res_mgr_mu_);
|
||||
cache_res_handle_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle>
|
||||
cache_res_handle_;
|
||||
};
|
||||
|
||||
explicit ConcurrentCacheReservationManager(
|
||||
std::shared_ptr<CacheReservationManager> cache_res_mgr) {
|
||||
cache_res_mgr_ = std::move(cache_res_mgr);
|
||||
}
|
||||
ConcurrentCacheReservationManager(const ConcurrentCacheReservationManager &) =
|
||||
delete;
|
||||
ConcurrentCacheReservationManager &operator=(
|
||||
const ConcurrentCacheReservationManager &) = delete;
|
||||
ConcurrentCacheReservationManager(ConcurrentCacheReservationManager &&) =
|
||||
delete;
|
||||
ConcurrentCacheReservationManager &operator=(
|
||||
ConcurrentCacheReservationManager &&) = delete;
|
||||
|
||||
~ConcurrentCacheReservationManager() override {}
|
||||
|
||||
inline Status UpdateCacheReservation(std::size_t new_memory_used) override {
|
||||
std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
|
||||
return cache_res_mgr_->UpdateCacheReservation(new_memory_used);
|
||||
}
|
||||
|
||||
inline Status UpdateCacheReservation(std::size_t memory_used_delta,
|
||||
bool increase) override {
|
||||
std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
|
||||
std::size_t total_mem_used = cache_res_mgr_->GetTotalMemoryUsed();
|
||||
Status s;
|
||||
if (!increase) {
|
||||
assert(total_mem_used >= memory_used_delta);
|
||||
s = cache_res_mgr_->UpdateCacheReservation(total_mem_used -
|
||||
memory_used_delta);
|
||||
} else {
|
||||
s = cache_res_mgr_->UpdateCacheReservation(total_mem_used +
|
||||
memory_used_delta);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
inline Status MakeCacheReservation(
|
||||
std::size_t incremental_memory_used,
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle> *handle)
|
||||
override {
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle>
|
||||
wrapped_handle;
|
||||
Status s;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
|
||||
s = cache_res_mgr_->MakeCacheReservation(incremental_memory_used,
|
||||
&wrapped_handle);
|
||||
}
|
||||
(*handle).reset(
|
||||
new ConcurrentCacheReservationManager::CacheReservationHandle(
|
||||
std::enable_shared_from_this<
|
||||
ConcurrentCacheReservationManager>::shared_from_this(),
|
||||
std::move(wrapped_handle)));
|
||||
return s;
|
||||
}
|
||||
inline std::size_t GetTotalReservedCacheSize() override {
|
||||
return cache_res_mgr_->GetTotalReservedCacheSize();
|
||||
}
|
||||
inline std::size_t GetTotalMemoryUsed() override {
|
||||
std::lock_guard<std::mutex> lock(cache_res_mgr_mu_);
|
||||
return cache_res_mgr_->GetTotalMemoryUsed();
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex cache_res_mgr_mu_;
|
||||
std::shared_ptr<CacheReservationManager> cache_res_mgr_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+117
-174
@@ -15,48 +15,57 @@
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class CacheReservationManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
static constexpr std::size_t kSizeDummyEntry =
|
||||
CacheReservationManagerImpl<CacheEntryRole::kMisc>::GetDummyEntrySize();
|
||||
static constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry;
|
||||
static constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
static constexpr int kNumShardBits = 0; // 2^0 shard
|
||||
|
||||
static constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
static const std::size_t kCacheKeyPrefixSize =
|
||||
BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length;
|
||||
static constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(kCacheCapacity, kNumShardBits);
|
||||
std::shared_ptr<CacheReservationManager> test_cache_rev_mng;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(kOneGigabyte, kNumShardBits);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng;
|
||||
|
||||
CacheReservationManagerTest() {
|
||||
test_cache_rev_mng =
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache);
|
||||
test_cache_rev_mng.reset(new CacheReservationManager(cache));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CacheReservationManagerTest, GenerateCacheKey) {
|
||||
// The first cache reservation manager owning the cache will have
|
||||
// cache->NewId() = 1
|
||||
constexpr std::size_t kCacheNewId = 1;
|
||||
// The first key generated inside of cache reservation manager will have
|
||||
// next_cache_key_id = 0
|
||||
constexpr std::size_t kCacheKeyId = 0;
|
||||
|
||||
char expected_cache_key[kCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
std::memset(expected_cache_key, 0, kCacheKeyPrefixSize + kMaxVarint64Length);
|
||||
|
||||
EncodeVarint64(expected_cache_key, kCacheNewId);
|
||||
char* end =
|
||||
EncodeVarint64(expected_cache_key + kCacheKeyPrefixSize, kCacheKeyId);
|
||||
Slice expected_cache_key_slice(
|
||||
expected_cache_key, static_cast<std::size_t>(end - expected_cache_key));
|
||||
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
// Next unique Cache key
|
||||
CacheKey ckey = CacheKey::CreateUniqueForCacheLifetime(cache.get());
|
||||
// Get to the underlying values
|
||||
uint64_t* ckey_data = reinterpret_cast<uint64_t*>(&ckey);
|
||||
// Back it up to the one used by CRM (using CacheKey implementation details)
|
||||
ckey_data[1]--;
|
||||
|
||||
// Specific key (subject to implementation details)
|
||||
EXPECT_EQ(ckey_data[0], 0);
|
||||
EXPECT_EQ(ckey_data[1], 2);
|
||||
|
||||
Cache::Handle* handle = cache->Lookup(ckey.AsSlice());
|
||||
Cache::Handle* handle = cache->Lookup(expected_cache_key_slice);
|
||||
EXPECT_NE(handle, nullptr)
|
||||
<< "Failed to generate the cache key for the dummy entry correctly";
|
||||
// Clean up the returned handle from Lookup() to prevent memory leak
|
||||
@@ -65,17 +74,21 @@ TEST_F(CacheReservationManagerTest, GenerateCacheKey) {
|
||||
|
||||
TEST_F(CacheReservationManagerTest, KeepCacheReservationTheSame) {
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry);
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used);
|
||||
std::size_t initial_pinned_usage = cache->GetPinnedUsage();
|
||||
ASSERT_GE(initial_pinned_usage, 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(initial_pinned_usage,
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to keep cache reservation the same when new_mem_used equals "
|
||||
"to current cache reservation";
|
||||
@@ -83,9 +96,6 @@ TEST_F(CacheReservationManagerTest, KeepCacheReservationTheSame) {
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when new_mem_used equals to current "
|
||||
"cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly when new_mem_used "
|
||||
"equals to current cache reservation";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to keep underlying dummy entries the same when new_mem_used "
|
||||
"equals to current cache reservation";
|
||||
@@ -94,14 +104,15 @@ TEST_F(CacheReservationManagerTest, KeepCacheReservationTheSame) {
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
IncreaseCacheReservationByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
@@ -112,14 +123,15 @@ TEST_F(CacheReservationManagerTest,
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
IncreaseCacheReservationNotByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry + kSizeDummyEntry / 2;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
3 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 3 * kSizeDummyEntry)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
@@ -129,47 +141,46 @@ TEST_F(CacheReservationManagerTest,
|
||||
|
||||
TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest,
|
||||
IncreaseCacheReservationOnFullCache) {
|
||||
;
|
||||
constexpr std::size_t kSizeDummyEntry =
|
||||
CacheReservationManagerImpl<CacheEntryRole::kMisc>::GetDummyEntrySize();
|
||||
constexpr std::size_t kSmallCacheCapacity = 4 * kSizeDummyEntry;
|
||||
constexpr std::size_t kBigCacheCapacity = 4096 * kSizeDummyEntry;
|
||||
constexpr std::size_t kOneMegabyte = 1024 * 1024;
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kSmallCacheCapacity;
|
||||
lo.capacity = kOneMegabyte;
|
||||
lo.num_shard_bits = 0; // 2^0 shard
|
||||
lo.strict_capacity_limit = true;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
std::shared_ptr<CacheReservationManager> test_cache_rev_mng =
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache));
|
||||
|
||||
std::size_t new_mem_used = kSmallCacheCapacity + 1;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
EXPECT_EQ(s, Status::MemoryLimit())
|
||||
std::size_t new_mem_used = kOneMegabyte + 1;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::Incomplete())
|
||||
<< "Failed to return status to indicate failure of dummy entry insertion "
|
||||
"during cache reservation on full cache";
|
||||
EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly before cache resevation failure happens "
|
||||
"due to full cache";
|
||||
EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
kSmallCacheCapacity)
|
||||
EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), kOneMegabyte)
|
||||
<< "Failed to bookkeep correctly (i.e, bookkeep only successful dummy "
|
||||
"entry insertions) when encountering cache resevation failure due to "
|
||||
"full cache";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
EXPECT_LE(cache->GetPinnedUsage(), kSmallCacheCapacity)
|
||||
EXPECT_LE(cache->GetPinnedUsage(), kOneMegabyte)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
|
||||
new_mem_used = kSmallCacheCapacity / 2; // 2 dummy entries
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
new_mem_used = kOneMegabyte / 2; // 2 dummy entries
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation after encountering cache "
|
||||
"reservation failure due to full cache";
|
||||
@@ -177,8 +188,6 @@ TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest,
|
||||
2 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly after "
|
||||
"encountering cache reservation due to full cache";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry)
|
||||
<< "Failed to release underlying dummy entries correctly on cache "
|
||||
"reservation decrease after encountering cache resevation failure due "
|
||||
@@ -190,34 +199,35 @@ TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest,
|
||||
"to full cache";
|
||||
|
||||
// Create cache full again for subsequent tests
|
||||
new_mem_used = kSmallCacheCapacity + 1;
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
EXPECT_EQ(s, Status::MemoryLimit())
|
||||
new_mem_used = kOneMegabyte + 1;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::Incomplete())
|
||||
<< "Failed to return status to indicate failure of dummy entry insertion "
|
||||
"during cache reservation on full cache";
|
||||
EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly before cache resevation failure happens "
|
||||
"due to full cache";
|
||||
EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
kSmallCacheCapacity)
|
||||
EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), kOneMegabyte)
|
||||
<< "Failed to bookkeep correctly (i.e, bookkeep only successful dummy "
|
||||
"entry insertions) when encountering cache resevation failure due to "
|
||||
"full cache";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
EXPECT_LE(cache->GetPinnedUsage(), kSmallCacheCapacity)
|
||||
EXPECT_LE(cache->GetPinnedUsage(), kOneMegabyte)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
|
||||
// Increase cache capacity so the previously failed insertion can fully
|
||||
// succeed
|
||||
cache->SetCapacity(kBigCacheCapacity);
|
||||
new_mem_used = kSmallCacheCapacity + 1;
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
cache->SetCapacity(kOneGigabyte);
|
||||
new_mem_used = kOneMegabyte + 1;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation after increasing cache capacity "
|
||||
"and mitigating cache full error";
|
||||
@@ -225,8 +235,6 @@ TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest,
|
||||
5 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly after "
|
||||
"increasing cache capacity and mitigating cache full error";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 5 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly after increasing "
|
||||
"cache capacity and mitigating cache full error";
|
||||
@@ -239,24 +247,26 @@ TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest,
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
DecreaseCacheReservationByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry);
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used);
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = 1 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
@@ -267,24 +277,26 @@ TEST_F(CacheReservationManagerTest,
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
DecreaseCacheReservationNotByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry);
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used);
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = kSizeDummyEntry / 2;
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
@@ -294,56 +306,58 @@ TEST_F(CacheReservationManagerTest,
|
||||
|
||||
TEST(CacheReservationManagerWithDelayedDecreaseTest,
|
||||
DecreaseCacheReservationWithDelayedDecrease) {
|
||||
constexpr std::size_t kSizeDummyEntry =
|
||||
CacheReservationManagerImpl<CacheEntryRole::kMisc>::GetDummyEntrySize();
|
||||
constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry;
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kCacheCapacity;
|
||||
lo.capacity = kOneGigabyte;
|
||||
lo.num_shard_bits = 0;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
std::shared_ptr<CacheReservationManager> test_cache_rev_mng =
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache, true /* delayed_decrease */);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache, true /* delayed_decrease */));
|
||||
|
||||
std::size_t new_mem_used = 8 * kSizeDummyEntry;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry);
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used);
|
||||
std::size_t initial_pinned_usage = cache->GetPinnedUsage();
|
||||
ASSERT_GE(initial_pinned_usage, 8 * kSizeDummyEntry);
|
||||
ASSERT_LT(initial_pinned_usage,
|
||||
8 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = 6 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK()) << "Failed to delay decreasing cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when delaying cache reservation "
|
||||
"decrease";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to delay decreasing underlying dummy entries in cache";
|
||||
|
||||
new_mem_used = 7 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK()) << "Failed to delay decreasing cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when delaying cache reservation "
|
||||
"decrease";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to delay decreasing underlying dummy entries in cache";
|
||||
|
||||
new_mem_used = 6 * kSizeDummyEntry - 1;
|
||||
s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly when new_mem_used < "
|
||||
"GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode";
|
||||
@@ -351,8 +365,6 @@ TEST(CacheReservationManagerWithDelayedDecreaseTest,
|
||||
6 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when new_mem_used < "
|
||||
"GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), new_mem_used)
|
||||
<< "Failed to bookkeep the used memory correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 6 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache when "
|
||||
"new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed "
|
||||
@@ -366,21 +378,22 @@ TEST(CacheReservationManagerWithDelayedDecreaseTest,
|
||||
|
||||
TEST(CacheReservationManagerDestructorTest,
|
||||
ReleaseRemainingDummyEntriesOnDestruction) {
|
||||
constexpr std::size_t kSizeDummyEntry =
|
||||
CacheReservationManagerImpl<CacheEntryRole::kMisc>::GetDummyEntrySize();
|
||||
constexpr std::size_t kCacheCapacity = 4096 * kSizeDummyEntry;
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kCacheCapacity;
|
||||
lo.capacity = kOneGigabyte;
|
||||
lo.num_shard_bits = 0;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
{
|
||||
std::shared_ptr<CacheReservationManager> test_cache_rev_mng =
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache));
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s = test_cache_rev_mng->UpdateCacheReservation(new_mem_used);
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
@@ -390,79 +403,9 @@ TEST(CacheReservationManagerDestructorTest,
|
||||
<< "Failed to release remaining underlying dummy entries in cache in "
|
||||
"CacheReservationManager's destructor";
|
||||
}
|
||||
|
||||
TEST(CacheReservationHandleTest, HandleTest) {
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kOneGigabyte;
|
||||
lo.num_shard_bits = 0;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
|
||||
std::shared_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
|
||||
cache));
|
||||
|
||||
std::size_t mem_used = 0;
|
||||
const std::size_t incremental_mem_used_handle_1 = 1 * kSizeDummyEntry;
|
||||
const std::size_t incremental_mem_used_handle_2 = 2 * kSizeDummyEntry;
|
||||
std::unique_ptr<CacheReservationManager::CacheReservationHandle> handle_1,
|
||||
handle_2;
|
||||
|
||||
// To test consecutive CacheReservationManager::MakeCacheReservation works
|
||||
// correctly in terms of returning the handle as well as updating cache
|
||||
// reservation and the latest total memory used
|
||||
Status s = test_cache_rev_mng->MakeCacheReservation(
|
||||
incremental_mem_used_handle_1, &handle_1);
|
||||
mem_used = mem_used + incremental_mem_used_handle_1;
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
EXPECT_TRUE(handle_1 != nullptr);
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used);
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used);
|
||||
EXPECT_GE(cache->GetPinnedUsage(), mem_used);
|
||||
EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead);
|
||||
|
||||
s = test_cache_rev_mng->MakeCacheReservation(incremental_mem_used_handle_2,
|
||||
&handle_2);
|
||||
mem_used = mem_used + incremental_mem_used_handle_2;
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
EXPECT_TRUE(handle_2 != nullptr);
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used);
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used);
|
||||
EXPECT_GE(cache->GetPinnedUsage(), mem_used);
|
||||
EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead);
|
||||
|
||||
// To test
|
||||
// CacheReservationManager::CacheReservationHandle::~CacheReservationHandle()
|
||||
// works correctly in releasing the cache reserved for the handle
|
||||
handle_1.reset();
|
||||
EXPECT_TRUE(handle_1 == nullptr);
|
||||
mem_used = mem_used - incremental_mem_used_handle_1;
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(), mem_used);
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalMemoryUsed(), mem_used);
|
||||
EXPECT_GE(cache->GetPinnedUsage(), mem_used);
|
||||
EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead);
|
||||
|
||||
// To test the actual CacheReservationManager object won't be deallocated
|
||||
// as long as there remain handles pointing to it.
|
||||
// We strongly recommend deallocating CacheReservationManager object only
|
||||
// after all its handles are deallocated to keep things easy to reasonate
|
||||
test_cache_rev_mng.reset();
|
||||
EXPECT_GE(cache->GetPinnedUsage(), mem_used);
|
||||
EXPECT_LT(cache->GetPinnedUsage(), mem_used + kMetaDataChargeOverhead);
|
||||
|
||||
handle_2.reset();
|
||||
// The CacheReservationManager object is now deallocated since all the handles
|
||||
// and its original pointer is gone
|
||||
mem_used = mem_used - incremental_mem_used_handle_2;
|
||||
EXPECT_EQ(mem_used, 0);
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), mem_used);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
}
|
||||
Vendored
+112
-329
@@ -14,83 +14,45 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cache/fast_lru_cache.h"
|
||||
#include "cache/clock_cache.h"
|
||||
#include "cache/lru_cache.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
// FastLRUCache and HyperClockCache only support 16-byte keys, so some of
|
||||
// the tests originally wrote for LRUCache do not work on the other caches.
|
||||
// Those tests were adapted to use 16-byte keys. We kept the original ones.
|
||||
// TODO: Remove the original tests if they ever become unused.
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
// Conversions between numeric keys/values and the types expected by Cache.
|
||||
std::string EncodeKey16Bytes(int k) {
|
||||
std::string result;
|
||||
PutFixed32(&result, k);
|
||||
result.append(std::string(12, 'a')); // Because we need a 16B output, we
|
||||
// add a 12-byte padding.
|
||||
return result;
|
||||
}
|
||||
|
||||
int DecodeKey16Bytes(const Slice& k) {
|
||||
assert(k.size() == 16);
|
||||
return DecodeFixed32(k.data()); // Decodes only the first 4 bytes of k.
|
||||
}
|
||||
|
||||
std::string EncodeKey32Bits(int k) {
|
||||
static std::string EncodeKey(int k) {
|
||||
std::string result;
|
||||
PutFixed32(&result, k);
|
||||
return result;
|
||||
}
|
||||
|
||||
int DecodeKey32Bits(const Slice& k) {
|
||||
static int DecodeKey(const Slice& k) {
|
||||
assert(k.size() == 4);
|
||||
return DecodeFixed32(k.data());
|
||||
}
|
||||
|
||||
void* EncodeValue(uintptr_t v) { return reinterpret_cast<void*>(v); }
|
||||
|
||||
int DecodeValue(void* v) {
|
||||
static void* EncodeValue(uintptr_t v) { return reinterpret_cast<void*>(v); }
|
||||
static int DecodeValue(void* v) {
|
||||
return static_cast<int>(reinterpret_cast<uintptr_t>(v));
|
||||
}
|
||||
|
||||
void DumbDeleter(const Slice& /*key*/, void* /*value*/) {}
|
||||
const std::string kLRU = "lru";
|
||||
const std::string kClock = "clock";
|
||||
|
||||
void EraseDeleter1(const Slice& /*key*/, void* value) {
|
||||
void dumbDeleter(const Slice& /*key*/, void* /*value*/) {}
|
||||
|
||||
void eraseDeleter(const Slice& /*key*/, void* value) {
|
||||
Cache* cache = reinterpret_cast<Cache*>(value);
|
||||
cache->Erase("foo");
|
||||
}
|
||||
|
||||
void EraseDeleter2(const Slice& /*key*/, void* value) {
|
||||
Cache* cache = reinterpret_cast<Cache*>(value);
|
||||
cache->Erase(EncodeKey16Bytes(1234));
|
||||
}
|
||||
|
||||
const std::string kLRU = "lru";
|
||||
const std::string kHyperClock = "hyper_clock";
|
||||
const std::string kFast = "fast";
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class CacheTest : public testing::TestWithParam<std::string> {
|
||||
public:
|
||||
static CacheTest* current_;
|
||||
static std::string type_;
|
||||
|
||||
static void Deleter(const Slice& key, void* v) {
|
||||
if (type_ == kFast || type_ == kHyperClock) {
|
||||
current_->deleted_keys_.push_back(DecodeKey16Bytes(key));
|
||||
} else {
|
||||
current_->deleted_keys_.push_back(DecodeKey32Bits(key));
|
||||
}
|
||||
current_->deleted_keys_.push_back(DecodeKey(key));
|
||||
current_->deleted_values_.push_back(DecodeValue(v));
|
||||
}
|
||||
|
||||
@@ -105,13 +67,10 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<Cache> cache2_;
|
||||
|
||||
size_t estimated_value_size_ = 1;
|
||||
|
||||
CacheTest()
|
||||
: cache_(NewCache(kCacheSize, kNumShardBits, false)),
|
||||
cache2_(NewCache(kCacheSize2, kNumShardBits2, false)) {
|
||||
current_ = this;
|
||||
type_ = GetParam();
|
||||
}
|
||||
|
||||
~CacheTest() override {}
|
||||
@@ -121,15 +80,8 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
if (type == kLRU) {
|
||||
return NewLRUCache(capacity);
|
||||
}
|
||||
if (type == kHyperClock) {
|
||||
return HyperClockCacheOptions(
|
||||
capacity, estimated_value_size_ /*estimated_value_size*/)
|
||||
.MakeSharedCache();
|
||||
}
|
||||
if (type == kFast) {
|
||||
return NewFastLRUCache(
|
||||
capacity, estimated_value_size_, -1 /*num_shard_bits*/,
|
||||
false /*strict_capacity_limit*/, kDefaultCacheMetadataChargePolicy);
|
||||
if (type == kClock) {
|
||||
return NewClockCache(capacity);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -147,42 +99,13 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
co.metadata_charge_policy = charge_policy;
|
||||
return NewLRUCache(co);
|
||||
}
|
||||
if (type == kHyperClock) {
|
||||
return HyperClockCacheOptions(capacity, 1 /*estimated_value_size*/,
|
||||
num_shard_bits, strict_capacity_limit,
|
||||
nullptr /*allocator*/, charge_policy)
|
||||
.MakeSharedCache();
|
||||
}
|
||||
if (type == kFast) {
|
||||
return NewFastLRUCache(capacity, 1 /*estimated_value_size*/,
|
||||
num_shard_bits, strict_capacity_limit,
|
||||
charge_policy);
|
||||
if (type == kClock) {
|
||||
return NewClockCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
charge_policy);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// These functions encode/decode keys in tests cases that use
|
||||
// int keys.
|
||||
// Currently, HyperClockCache requires keys to be 16B long, whereas
|
||||
// LRUCache doesn't, so the encoding depends on the cache type.
|
||||
std::string EncodeKey(int k) {
|
||||
auto type = GetParam();
|
||||
if (type == kFast || type == kHyperClock) {
|
||||
return EncodeKey16Bytes(k);
|
||||
} else {
|
||||
return EncodeKey32Bits(k);
|
||||
}
|
||||
}
|
||||
|
||||
int DecodeKey(const Slice& k) {
|
||||
auto type = GetParam();
|
||||
if (type == kFast || type == kHyperClock) {
|
||||
return DecodeKey16Bytes(k);
|
||||
} else {
|
||||
return DecodeKey32Bits(k);
|
||||
}
|
||||
}
|
||||
|
||||
int Lookup(std::shared_ptr<Cache> cache, int key) {
|
||||
Cache::Handle* handle = cache->Lookup(EncodeKey(key));
|
||||
const int r = (handle == nullptr) ? -1 : DecodeValue(cache->Value(handle));
|
||||
@@ -226,101 +149,59 @@ class CacheTest : public testing::TestWithParam<std::string> {
|
||||
Erase(cache2_, key);
|
||||
}
|
||||
};
|
||||
|
||||
CacheTest* CacheTest::current_;
|
||||
std::string CacheTest::type_;
|
||||
|
||||
class LRUCacheTest : public CacheTest {};
|
||||
|
||||
TEST_P(CacheTest, UsageTest) {
|
||||
auto type = GetParam();
|
||||
|
||||
// cache is std::shared_ptr and will be automatically cleaned up.
|
||||
const size_t kCapacity = 100000;
|
||||
const uint64_t kCapacity = 100000;
|
||||
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
|
||||
auto precise_cache = NewCache(kCapacity, 0, false, kFullChargeCacheMetadata);
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
size_t baseline_meta_usage = precise_cache->GetUsage();
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_EQ(0, baseline_meta_usage);
|
||||
}
|
||||
ASSERT_EQ(0, precise_cache->GetUsage());
|
||||
|
||||
size_t usage = 0;
|
||||
char value[10] = "abcdef";
|
||||
// make sure everything will be cached
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::string(i, 'a');
|
||||
} else {
|
||||
key = EncodeKey(i);
|
||||
}
|
||||
std::string key(i, 'a');
|
||||
auto kv_size = key.size() + 5;
|
||||
ASSERT_OK(cache->Insert(key, reinterpret_cast<void*>(value), kv_size,
|
||||
DumbDeleter));
|
||||
dumbDeleter));
|
||||
ASSERT_OK(precise_cache->Insert(key, reinterpret_cast<void*>(value),
|
||||
kv_size, DumbDeleter));
|
||||
kv_size, dumbDeleter));
|
||||
usage += kv_size;
|
||||
ASSERT_EQ(usage, cache->GetUsage());
|
||||
if (type == kHyperClock) {
|
||||
ASSERT_EQ(baseline_meta_usage + usage, precise_cache->GetUsage());
|
||||
} else {
|
||||
ASSERT_LT(usage, precise_cache->GetUsage());
|
||||
}
|
||||
ASSERT_LT(usage, precise_cache->GetUsage());
|
||||
}
|
||||
|
||||
cache->EraseUnRefEntries();
|
||||
precise_cache->EraseUnRefEntries();
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
ASSERT_EQ(baseline_meta_usage, precise_cache->GetUsage());
|
||||
ASSERT_EQ(0, precise_cache->GetUsage());
|
||||
|
||||
// make sure the cache will be overloaded
|
||||
for (size_t i = 1; i < kCapacity; ++i) {
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::to_string(i);
|
||||
} else {
|
||||
key = EncodeKey(static_cast<int>(1000 + i));
|
||||
}
|
||||
for (uint64_t i = 1; i < kCapacity; ++i) {
|
||||
auto key = ToString(i);
|
||||
ASSERT_OK(cache->Insert(key, reinterpret_cast<void*>(value), key.size() + 5,
|
||||
DumbDeleter));
|
||||
dumbDeleter));
|
||||
ASSERT_OK(precise_cache->Insert(key, reinterpret_cast<void*>(value),
|
||||
key.size() + 5, DumbDeleter));
|
||||
key.size() + 5, dumbDeleter));
|
||||
}
|
||||
|
||||
// the usage should be close to the capacity
|
||||
ASSERT_GT(kCapacity, cache->GetUsage());
|
||||
ASSERT_GT(kCapacity, precise_cache->GetUsage());
|
||||
ASSERT_LT(kCapacity * 0.95, cache->GetUsage());
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_LT(kCapacity * 0.95, precise_cache->GetUsage());
|
||||
} else {
|
||||
// estimated value size of 1 is weird for clock cache, because
|
||||
// almost all of the capacity will be used for metadata, and due to only
|
||||
// using power of 2 table sizes, we might hit strict occupancy limit
|
||||
// before hitting capacity limit.
|
||||
ASSERT_LT(kCapacity * 0.80, precise_cache->GetUsage());
|
||||
}
|
||||
ASSERT_LT(kCapacity * 0.95, precise_cache->GetUsage());
|
||||
}
|
||||
|
||||
// TODO: This test takes longer than expected on ClockCache. This is
|
||||
// because the values size estimate at construction is too sloppy.
|
||||
// Fix this.
|
||||
// Why is it so slow? The cache is constructed with an estimate of 1, but
|
||||
// then the charge is claimed to be 21. This will cause the hash table
|
||||
// to be extremely sparse, which in turn means clock needs to scan too
|
||||
// many slots to find victims.
|
||||
TEST_P(CacheTest, PinnedUsageTest) {
|
||||
auto type = GetParam();
|
||||
|
||||
// cache is std::shared_ptr and will be automatically cleaned up.
|
||||
const size_t kCapacity = 200000;
|
||||
const uint64_t kCapacity = 200000;
|
||||
auto cache = NewCache(kCapacity, 8, false, kDontChargeCacheMetadata);
|
||||
auto precise_cache = NewCache(kCapacity, 8, false, kFullChargeCacheMetadata);
|
||||
size_t baseline_meta_usage = precise_cache->GetUsage();
|
||||
if (type != kHyperClock) {
|
||||
ASSERT_EQ(0, baseline_meta_usage);
|
||||
}
|
||||
|
||||
size_t pinned_usage = 0;
|
||||
char value[10] = "abcdef";
|
||||
@@ -331,20 +212,15 @@ TEST_P(CacheTest, PinnedUsageTest) {
|
||||
// Add entries. Unpin some of them after insertion. Then, pin some of them
|
||||
// again. Check GetPinnedUsage().
|
||||
for (int i = 1; i < 100; ++i) {
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::string(i, 'a');
|
||||
} else {
|
||||
key = EncodeKey(i);
|
||||
}
|
||||
std::string key(i, 'a');
|
||||
auto kv_size = key.size() + 5;
|
||||
Cache::Handle* handle;
|
||||
Cache::Handle* handle_in_precise_cache;
|
||||
ASSERT_OK(cache->Insert(key, reinterpret_cast<void*>(value), kv_size,
|
||||
DumbDeleter, &handle));
|
||||
dumbDeleter, &handle));
|
||||
assert(handle);
|
||||
ASSERT_OK(precise_cache->Insert(key, reinterpret_cast<void*>(value),
|
||||
kv_size, DumbDeleter,
|
||||
kv_size, dumbDeleter,
|
||||
&handle_in_precise_cache));
|
||||
assert(handle_in_precise_cache);
|
||||
pinned_usage += kv_size;
|
||||
@@ -378,17 +254,12 @@ TEST_P(CacheTest, PinnedUsageTest) {
|
||||
ASSERT_LT(pinned_usage, precise_cache_pinned_usage);
|
||||
|
||||
// check that overloading the cache does not change the pinned usage
|
||||
for (size_t i = 1; i < 2 * kCapacity; ++i) {
|
||||
std::string key;
|
||||
if (type == kLRU) {
|
||||
key = std::to_string(i);
|
||||
} else {
|
||||
key = EncodeKey(static_cast<int>(1000 + i));
|
||||
}
|
||||
for (uint64_t i = 1; i < 2 * kCapacity; ++i) {
|
||||
auto key = ToString(i);
|
||||
ASSERT_OK(cache->Insert(key, reinterpret_cast<void*>(value), key.size() + 5,
|
||||
DumbDeleter));
|
||||
dumbDeleter));
|
||||
ASSERT_OK(precise_cache->Insert(key, reinterpret_cast<void*>(value),
|
||||
key.size() + 5, DumbDeleter));
|
||||
key.size() + 5, dumbDeleter));
|
||||
}
|
||||
ASSERT_EQ(pinned_usage, cache->GetPinnedUsage());
|
||||
ASSERT_EQ(precise_cache_pinned_usage, precise_cache->GetPinnedUsage());
|
||||
@@ -410,7 +281,7 @@ TEST_P(CacheTest, PinnedUsageTest) {
|
||||
cache->EraseUnRefEntries();
|
||||
precise_cache->EraseUnRefEntries();
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
ASSERT_EQ(baseline_meta_usage, precise_cache->GetUsage());
|
||||
ASSERT_EQ(0, precise_cache->GetUsage());
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, HitAndMiss) {
|
||||
@@ -427,30 +298,16 @@ TEST_P(CacheTest, HitAndMiss) {
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
Insert(100, 102);
|
||||
if (GetParam() == kHyperClock) {
|
||||
// ClockCache usually doesn't overwrite on Insert
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
} else {
|
||||
ASSERT_EQ(102, Lookup(100));
|
||||
}
|
||||
ASSERT_EQ(102, Lookup(100));
|
||||
ASSERT_EQ(201, Lookup(200));
|
||||
ASSERT_EQ(-1, Lookup(300));
|
||||
|
||||
ASSERT_EQ(1U, deleted_keys_.size());
|
||||
ASSERT_EQ(100, deleted_keys_[0]);
|
||||
if (GetParam() == kHyperClock) {
|
||||
ASSERT_EQ(102, deleted_values_[0]);
|
||||
} else {
|
||||
ASSERT_EQ(101, deleted_values_[0]);
|
||||
}
|
||||
ASSERT_EQ(101, deleted_values_[0]);
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, InsertSameKey) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"ClockCache doesn't guarantee Insert overwrite same key.");
|
||||
return;
|
||||
}
|
||||
Insert(1, 1);
|
||||
Insert(1, 2);
|
||||
ASSERT_EQ(2, Lookup(1));
|
||||
@@ -476,11 +333,6 @@ TEST_P(CacheTest, Erase) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EntriesArePinned) {
|
||||
if (GetParam() == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"ClockCache doesn't guarantee Insert overwrite same key.");
|
||||
return;
|
||||
}
|
||||
Insert(100, 101);
|
||||
Cache::Handle* h1 = cache_->Lookup(EncodeKey(100));
|
||||
ASSERT_EQ(101, DecodeValue(cache_->Value(h1)));
|
||||
@@ -513,8 +365,9 @@ TEST_P(CacheTest, EntriesArePinned) {
|
||||
TEST_P(CacheTest, EvictionPolicy) {
|
||||
Insert(100, 101);
|
||||
Insert(200, 201);
|
||||
|
||||
// Frequently used entry must be kept around
|
||||
for (int i = 0; i < 2 * kCacheSize; i++) {
|
||||
for (int i = 0; i < kCacheSize * 2; i++) {
|
||||
Insert(1000+i, 2000+i);
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
}
|
||||
@@ -541,12 +394,6 @@ TEST_P(CacheTest, ExternalRefPinsEntries) {
|
||||
for (int j = 0; j < 2 * kCacheSize + 100; j++) {
|
||||
Insert(1000 + j, 2000 + j);
|
||||
}
|
||||
// Clock cache is even more stateful and needs more churn to evict
|
||||
if (GetParam() == kHyperClock) {
|
||||
for (int j = 0; j < kCacheSize; j++) {
|
||||
Insert(11000 + j, 11000 + j);
|
||||
}
|
||||
}
|
||||
if (i < 2) {
|
||||
ASSERT_EQ(101, Lookup(100));
|
||||
}
|
||||
@@ -572,8 +419,8 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
Insert(302, 103);
|
||||
Insert(303, 104);
|
||||
|
||||
// Insert entries much more than cache capacity.
|
||||
for (int i = 0; i < 100 * kCacheSize; i++) {
|
||||
// Insert entries much more than Cache capacity
|
||||
for (int i = 0; i < kCacheSize * 2; i++) {
|
||||
Insert(1000 + i, 2000 + i);
|
||||
}
|
||||
|
||||
@@ -603,41 +450,20 @@ TEST_P(CacheTest, EvictionPolicyRef) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EvictEmptyCache) {
|
||||
auto type = GetParam();
|
||||
|
||||
// Insert item large than capacity to trigger eviction on empty cache.
|
||||
auto cache = NewCache(1, 0, false);
|
||||
if (type == kLRU) {
|
||||
ASSERT_OK(cache->Insert("foo", nullptr, 10, DumbDeleter));
|
||||
} else {
|
||||
ASSERT_OK(cache->Insert(EncodeKey(1000), nullptr, 10, DumbDeleter));
|
||||
}
|
||||
ASSERT_OK(cache->Insert("foo", nullptr, 10, dumbDeleter));
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, EraseFromDeleter) {
|
||||
auto type = GetParam();
|
||||
|
||||
// Have deleter which will erase item from cache, which will re-enter
|
||||
// the cache at that point.
|
||||
std::shared_ptr<Cache> cache = NewCache(10, 0, false);
|
||||
std::string foo, bar;
|
||||
Cache::DeleterFn erase_deleter;
|
||||
if (type == kLRU) {
|
||||
foo = "foo";
|
||||
bar = "bar";
|
||||
erase_deleter = EraseDeleter1;
|
||||
} else {
|
||||
foo = EncodeKey(1234);
|
||||
bar = EncodeKey(5678);
|
||||
erase_deleter = EraseDeleter2;
|
||||
}
|
||||
|
||||
ASSERT_OK(cache->Insert(foo, nullptr, 1, DumbDeleter));
|
||||
ASSERT_OK(cache->Insert(bar, cache.get(), 1, erase_deleter));
|
||||
|
||||
cache->Erase(bar);
|
||||
ASSERT_EQ(nullptr, cache->Lookup(foo));
|
||||
ASSERT_EQ(nullptr, cache->Lookup(bar));
|
||||
ASSERT_OK(cache->Insert("foo", nullptr, 1, dumbDeleter));
|
||||
ASSERT_OK(cache->Insert("bar", cache.get(), 1, eraseDeleter));
|
||||
cache->Erase("bar");
|
||||
ASSERT_EQ(nullptr, cache->Lookup("foo"));
|
||||
ASSERT_EQ(nullptr, cache->Lookup("bar"));
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, ErasedHandleState) {
|
||||
@@ -670,9 +496,9 @@ TEST_P(CacheTest, HeavyEntries) {
|
||||
const int kHeavy = 10;
|
||||
int added = 0;
|
||||
int index = 0;
|
||||
while (added < 2 * kCacheSize) {
|
||||
while (added < 2*kCacheSize) {
|
||||
const int weight = (index & 1) ? kLight : kHeavy;
|
||||
Insert(index, 1000 + index, weight);
|
||||
Insert(index, 1000+index, weight);
|
||||
added += weight;
|
||||
index++;
|
||||
}
|
||||
@@ -683,7 +509,7 @@ TEST_P(CacheTest, HeavyEntries) {
|
||||
int r = Lookup(i);
|
||||
if (r >= 0) {
|
||||
cached_weight += weight;
|
||||
ASSERT_EQ(1000 + i, r);
|
||||
ASSERT_EQ(1000+i, r);
|
||||
}
|
||||
}
|
||||
ASSERT_LE(cached_weight, kCacheSize + kCacheSize/10);
|
||||
@@ -695,11 +521,12 @@ TEST_P(CacheTest, NewId) {
|
||||
ASSERT_NE(a, b);
|
||||
}
|
||||
|
||||
|
||||
class Value {
|
||||
public:
|
||||
explicit Value(int v) : v_(v) {}
|
||||
explicit Value(size_t v) : v_(v) { }
|
||||
|
||||
int v_;
|
||||
size_t v_;
|
||||
};
|
||||
|
||||
namespace {
|
||||
@@ -740,13 +567,6 @@ TEST_P(CacheTest, ReleaseWithoutErase) {
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, SetCapacity) {
|
||||
auto type = GetParam();
|
||||
if (type == kFast || type == kHyperClock) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"FastLRUCache and HyperClockCache don't support arbitrary capacity "
|
||||
"adjustments.");
|
||||
return;
|
||||
}
|
||||
// test1: increase capacity
|
||||
// lets create a cache with capacity 5,
|
||||
// then, insert 5 elements, then increase capacity
|
||||
@@ -754,8 +574,8 @@ TEST_P(CacheTest, SetCapacity) {
|
||||
std::shared_ptr<Cache> cache = NewCache(5, 0, false);
|
||||
std::vector<Cache::Handle*> handles(10);
|
||||
// Insert 5 entries, but not releasing.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
std::string key = ToString(i+1);
|
||||
Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
@@ -769,14 +589,14 @@ TEST_P(CacheTest, SetCapacity) {
|
||||
// insert 5 more elements to cache, then release 5,
|
||||
// then decrease capacity to 7, final capacity should be 7
|
||||
// and usage should be 7
|
||||
for (int i = 5; i < 10; i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
for (size_t i = 5; i < 10; i++) {
|
||||
std::string key = ToString(i+1);
|
||||
Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
ASSERT_EQ(10U, cache->GetCapacity());
|
||||
ASSERT_EQ(10U, cache->GetUsage());
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
ASSERT_EQ(10U, cache->GetCapacity());
|
||||
@@ -786,30 +606,19 @@ TEST_P(CacheTest, SetCapacity) {
|
||||
ASSERT_EQ(7, cache->GetUsage());
|
||||
|
||||
// release remaining 5 to keep valgrind happy
|
||||
for (int i = 5; i < 10; i++) {
|
||||
for (size_t i = 5; i < 10; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
|
||||
// Make sure this doesn't crash or upset ASAN/valgrind
|
||||
cache->DisownData();
|
||||
}
|
||||
|
||||
TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
|
||||
auto type = GetParam();
|
||||
if (type == kFast) {
|
||||
ROCKSDB_GTEST_BYPASS(
|
||||
"FastLRUCache only supports a limited number of "
|
||||
"inserts beyond "
|
||||
"capacity.");
|
||||
return;
|
||||
}
|
||||
// test1: set the flag to false. Insert more keys than capacity. See if they
|
||||
// all go through.
|
||||
std::shared_ptr<Cache> cache = NewCache(5, 0, false);
|
||||
std::vector<Cache::Handle*> handles(10);
|
||||
Status s;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
std::string key = ToString(i + 1);
|
||||
s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(nullptr, handles[i]);
|
||||
@@ -817,29 +626,29 @@ TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
|
||||
ASSERT_EQ(10, cache->GetUsage());
|
||||
|
||||
// test2: set the flag to true. Insert and check if it fails.
|
||||
std::string extra_key = EncodeKey(100);
|
||||
std::string extra_key = "extra";
|
||||
Value* extra_value = new Value(0);
|
||||
cache->SetStrictCapacityLimit(true);
|
||||
Cache::Handle* handle;
|
||||
s = cache->Insert(extra_key, extra_value, 1, &deleter, &handle);
|
||||
ASSERT_TRUE(s.IsMemoryLimit());
|
||||
ASSERT_TRUE(s.IsIncomplete());
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
ASSERT_EQ(10, cache->GetUsage());
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (size_t i = 0; i < 10; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
|
||||
// test3: init with flag being true.
|
||||
std::shared_ptr<Cache> cache2 = NewCache(5, 0, true);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
std::string key = ToString(i + 1);
|
||||
s = cache2->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(nullptr, handles[i]);
|
||||
}
|
||||
s = cache2->Insert(extra_key, extra_value, 1, &deleter, &handle);
|
||||
ASSERT_TRUE(s.IsMemoryLimit());
|
||||
ASSERT_TRUE(s.IsIncomplete());
|
||||
ASSERT_EQ(nullptr, handle);
|
||||
// test insert without handle
|
||||
s = cache2->Insert(extra_key, extra_value, 1, &deleter);
|
||||
@@ -848,7 +657,7 @@ TEST_P(LRUCacheTest, SetStrictCapacityLimit) {
|
||||
ASSERT_EQ(5, cache2->GetUsage());
|
||||
ASSERT_EQ(nullptr, cache2->Lookup(extra_key));
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
cache2->Release(handles[i]);
|
||||
}
|
||||
}
|
||||
@@ -862,15 +671,15 @@ TEST_P(CacheTest, OverCapacity) {
|
||||
std::vector<Cache::Handle*> handles(n+1);
|
||||
|
||||
// Insert n+1 entries, but not releasing.
|
||||
for (int i = 0; i < static_cast<int>(n + 1); i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
for (size_t i = 0; i < n + 1; i++) {
|
||||
std::string key = ToString(i+1);
|
||||
Status s = cache->Insert(key, new Value(i + 1), 1, &deleter, &handles[i]);
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
// Guess what's in the cache now?
|
||||
for (int i = 0; i < static_cast<int>(n + 1); i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
for (size_t i = 0; i < n + 1; i++) {
|
||||
std::string key = ToString(i+1);
|
||||
auto h = cache->Lookup(key);
|
||||
ASSERT_TRUE(h != nullptr);
|
||||
if (h) cache->Release(h);
|
||||
@@ -878,37 +687,26 @@ TEST_P(CacheTest, OverCapacity) {
|
||||
|
||||
// the cache is over capacity since nothing could be evicted
|
||||
ASSERT_EQ(n + 1U, cache->GetUsage());
|
||||
for (int i = 0; i < static_cast<int>(n + 1); i++) {
|
||||
for (size_t i = 0; i < n + 1; i++) {
|
||||
cache->Release(handles[i]);
|
||||
}
|
||||
// Make sure eviction is triggered.
|
||||
cache->SetCapacity(n);
|
||||
|
||||
if (GetParam() == kHyperClock) {
|
||||
// Make sure eviction is triggered.
|
||||
ASSERT_OK(cache->Insert(EncodeKey(-1), nullptr, 1, &deleter, &handles[0]));
|
||||
// cache is under capacity now since elements were released
|
||||
ASSERT_EQ(n, cache->GetUsage());
|
||||
|
||||
// cache is under capacity now since elements were released
|
||||
ASSERT_GE(n, cache->GetUsage());
|
||||
|
||||
// clean up
|
||||
cache->Release(handles[0]);
|
||||
} else {
|
||||
// LRUCache checks for over-capacity in Release.
|
||||
|
||||
// cache is exactly at capacity now with minimal eviction
|
||||
ASSERT_EQ(n, cache->GetUsage());
|
||||
|
||||
// element 0 is evicted and the rest is there
|
||||
// This is consistent with the LRU policy since the element 0
|
||||
// was released first
|
||||
for (int i = 0; i < static_cast<int>(n + 1); i++) {
|
||||
std::string key = EncodeKey(i + 1);
|
||||
auto h = cache->Lookup(key);
|
||||
if (h) {
|
||||
ASSERT_NE(static_cast<size_t>(i), 0U);
|
||||
cache->Release(h);
|
||||
} else {
|
||||
ASSERT_EQ(static_cast<size_t>(i), 0U);
|
||||
}
|
||||
// element 0 is evicted and the rest is there
|
||||
// This is consistent with the LRU policy since the element 0
|
||||
// was released first
|
||||
for (size_t i = 0; i < n + 1; i++) {
|
||||
std::string key = ToString(i+1);
|
||||
auto h = cache->Lookup(key);
|
||||
if (h) {
|
||||
ASSERT_NE(i, 0U);
|
||||
cache->Release(h);
|
||||
} else {
|
||||
ASSERT_EQ(i, 0U);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -934,7 +732,7 @@ TEST_P(CacheTest, ApplyToAllCacheEntriesTest) {
|
||||
std::sort(inserted.begin(), inserted.end());
|
||||
std::sort(legacy_callback_state.begin(), legacy_callback_state.end());
|
||||
ASSERT_EQ(inserted.size(), legacy_callback_state.size());
|
||||
for (int i = 0; i < static_cast<int>(inserted.size()); ++i) {
|
||||
for (size_t i = 0; i < inserted.size(); ++i) {
|
||||
EXPECT_EQ(inserted[i], legacy_callback_state[i]);
|
||||
}
|
||||
}
|
||||
@@ -943,9 +741,9 @@ TEST_P(CacheTest, ApplyToAllEntriesTest) {
|
||||
std::vector<std::string> callback_state;
|
||||
const auto callback = [&](const Slice& key, void* value, size_t charge,
|
||||
Cache::DeleterFn deleter) {
|
||||
callback_state.push_back(std::to_string(DecodeKey(key)) + "," +
|
||||
std::to_string(DecodeValue(value)) + "," +
|
||||
std::to_string(charge));
|
||||
callback_state.push_back(ToString(DecodeKey(key)) + "," +
|
||||
ToString(DecodeValue(value)) + "," +
|
||||
ToString(charge));
|
||||
assert(deleter == &CacheTest::Deleter);
|
||||
};
|
||||
|
||||
@@ -954,15 +752,15 @@ TEST_P(CacheTest, ApplyToAllEntriesTest) {
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Insert(i, i * 2, i + 1);
|
||||
inserted.push_back(std::to_string(i) + "," + std::to_string(i * 2) + "," +
|
||||
std::to_string(i + 1));
|
||||
inserted.push_back(ToString(i) + "," + ToString(i * 2) + "," +
|
||||
ToString(i + 1));
|
||||
}
|
||||
cache_->ApplyToAllEntries(callback, /*opts*/ {});
|
||||
|
||||
std::sort(inserted.begin(), inserted.end());
|
||||
std::sort(callback_state.begin(), callback_state.end());
|
||||
ASSERT_EQ(inserted.size(), callback_state.size());
|
||||
for (int i = 0; i < static_cast<int>(inserted.size()); ++i) {
|
||||
for (size_t i = 0; i < inserted.size(); ++i) {
|
||||
EXPECT_EQ(inserted[i], callback_state[i]);
|
||||
}
|
||||
}
|
||||
@@ -977,10 +775,8 @@ TEST_P(CacheTest, ApplyToAllEntriesDuringResize) {
|
||||
constexpr int kSpecialCharge = 2;
|
||||
constexpr int kNotSpecialCharge = 1;
|
||||
constexpr int kSpecialCount = 100;
|
||||
size_t expected_usage = 0;
|
||||
for (int i = 0; i < kSpecialCount; ++i) {
|
||||
Insert(i, i * 2, kSpecialCharge);
|
||||
expected_usage += kSpecialCharge;
|
||||
}
|
||||
|
||||
// For callback
|
||||
@@ -1001,46 +797,29 @@ TEST_P(CacheTest, ApplyToAllEntriesDuringResize) {
|
||||
});
|
||||
|
||||
// In parallel, add more entries, enough to cause resize but not enough
|
||||
// to cause ejections. (Note: if any cache shard is over capacity, there
|
||||
// will be ejections)
|
||||
for (int i = kSpecialCount * 1; i < kSpecialCount * 5; ++i) {
|
||||
// to cause ejections
|
||||
for (int i = kSpecialCount * 1; i < kSpecialCount * 6; ++i) {
|
||||
Insert(i, i * 2, kNotSpecialCharge);
|
||||
expected_usage += kNotSpecialCharge;
|
||||
}
|
||||
|
||||
apply_thread.join();
|
||||
// verify no evictions
|
||||
ASSERT_EQ(cache_->GetUsage(), expected_usage);
|
||||
// verify everything seen in ApplyToAllEntries
|
||||
ASSERT_EQ(special_count, kSpecialCount);
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, DefaultShardBits) {
|
||||
// Prevent excessive allocation (to save time & space)
|
||||
estimated_value_size_ = 100000;
|
||||
// Implementations use different minimum shard sizes
|
||||
size_t min_shard_size =
|
||||
(GetParam() == kHyperClock ? 32U * 1024U : 512U) * 1024U;
|
||||
|
||||
std::shared_ptr<Cache> cache = NewCache(32U * min_shard_size);
|
||||
// test1: set the flag to false. Insert more keys than capacity. See if they
|
||||
// all go through.
|
||||
std::shared_ptr<Cache> cache = NewCache(16 * 1024L * 1024L);
|
||||
ShardedCache* sc = dynamic_cast<ShardedCache*>(cache.get());
|
||||
ASSERT_EQ(5, sc->GetNumShardBits());
|
||||
|
||||
cache = NewCache(min_shard_size / 1000U * 999U);
|
||||
cache = NewLRUCache(511 * 1024L, -1, true);
|
||||
sc = dynamic_cast<ShardedCache*>(cache.get());
|
||||
ASSERT_EQ(0, sc->GetNumShardBits());
|
||||
|
||||
cache = NewCache(3U * 1024U * 1024U * 1024U);
|
||||
cache = NewLRUCache(1024L * 1024L * 1024L, -1, true);
|
||||
sc = dynamic_cast<ShardedCache*>(cache.get());
|
||||
// current maximum of 6
|
||||
ASSERT_EQ(6, sc->GetNumShardBits());
|
||||
|
||||
if constexpr (sizeof(size_t) > 4) {
|
||||
cache = NewCache(128U * min_shard_size);
|
||||
sc = dynamic_cast<ShardedCache*>(cache.get());
|
||||
// current maximum of 6
|
||||
ASSERT_EQ(6, sc->GetNumShardBits());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, GetChargeAndDeleter) {
|
||||
@@ -1052,15 +831,19 @@ TEST_P(CacheTest, GetChargeAndDeleter) {
|
||||
cache_->Release(h1);
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_CLOCK_CACHE
|
||||
std::shared_ptr<Cache> (*new_clock_cache_func)(
|
||||
size_t, int, bool, CacheMetadataChargePolicy) = NewClockCache;
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest,
|
||||
testing::Values(kLRU, kHyperClock, kFast));
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, LRUCacheTest,
|
||||
testing::Values(kLRU, kFast));
|
||||
testing::Values(kLRU, kClock));
|
||||
#else
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, CacheTest, testing::Values(kLRU));
|
||||
#endif // SUPPORT_CLOCK_CACHE
|
||||
INSTANTIATE_TEST_CASE_P(CacheTestInstance, LRUCacheTest, testing::Values(kLRU));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Vendored
-117
@@ -1,117 +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/charged_cache.h"
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
ChargedCache::ChargedCache(std::shared_ptr<Cache> cache,
|
||||
std::shared_ptr<Cache> block_cache)
|
||||
: cache_(cache),
|
||||
cache_res_mgr_(std::make_shared<ConcurrentCacheReservationManager>(
|
||||
std::make_shared<
|
||||
CacheReservationManagerImpl<CacheEntryRole::kBlobCache>>(
|
||||
block_cache))) {}
|
||||
|
||||
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(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,
|
||||
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.
|
||||
assert(cache_res_mgr_);
|
||||
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 = cache_->GetUsage(handle);
|
||||
bool erased = cache_->Release(handle, useful, erase_if_last_ref);
|
||||
if (erased) {
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_
|
||||
->UpdateCacheReservation(memory_used_delta, /* increase */ false)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
return erased;
|
||||
}
|
||||
|
||||
bool ChargedCache::Release(Cache::Handle* handle, bool 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_
|
||||
->UpdateCacheReservation(memory_used_delta, /* increase */ false)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
return erased;
|
||||
}
|
||||
|
||||
void ChargedCache::Erase(const Slice& key) {
|
||||
cache_->Erase(key);
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void ChargedCache::EraseUnRefEntries() {
|
||||
cache_->EraseUnRefEntries();
|
||||
assert(cache_res_mgr_);
|
||||
cache_res_mgr_->UpdateCacheReservation(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
void ChargedCache::SetCapacity(size_t 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(cache_->GetUsage())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
-121
@@ -1,121 +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 <string>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
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 Cache {
|
||||
public:
|
||||
ChargedCache(std::shared_ptr<Cache> cache,
|
||||
std::shared_ptr<Cache> block_cache);
|
||||
~ChargedCache() override = default;
|
||||
|
||||
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,
|
||||
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;
|
||||
bool Release(Cache::Handle* handle, bool erase_if_last_ref = false) override;
|
||||
|
||||
void Erase(const Slice& key) override;
|
||||
void EraseUnRefEntries() override;
|
||||
|
||||
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;
|
||||
|
||||
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 {
|
||||
return cache_res_mgr_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
std::shared_ptr<ConcurrentCacheReservationManager> cache_res_mgr_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+748
-1142
File diff suppressed because it is too large.
Load diff
Vendored
+2
-608
@@ -9,614 +9,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#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/cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace hyper_clock_cache {
|
||||
|
||||
// Forward declaration of friend class.
|
||||
class ClockCacheTest;
|
||||
|
||||
// HyperClockCache is an experimental alternative to LRUCache.
|
||||
//
|
||||
// Benefits
|
||||
// --------
|
||||
// * Fully lock free (no waits or spins) for efficiency under high concurrency
|
||||
// * Optimized for hot path reads. For concurrency control, most Lookup() and
|
||||
// essentially all Release() are a single atomic add operation.
|
||||
// * Eviction on insertion is fully parallel and lock-free.
|
||||
// * Uses a generalized + aging variant of CLOCK eviction that might outperform
|
||||
// LRU in some cases. (For background, see
|
||||
// https://en.wikipedia.org/wiki/Page_replacement_algorithm)
|
||||
//
|
||||
// Costs
|
||||
// -----
|
||||
// * Hash table is not resizable (for lock-free efficiency) so capacity is not
|
||||
// dynamically changeable. Rely on an estimated average value (block) size for
|
||||
// space+time efficiency. (See estimated_entry_charge option details.)
|
||||
// * Insert usually does not (but might) overwrite a previous entry associated
|
||||
// with a cache key. This is OK for RocksDB uses of Cache.
|
||||
// * Only supports keys of exactly 16 bytes, which is what RocksDB uses for
|
||||
// block cache (not row cache or table cache).
|
||||
// * SecondaryCache is not supported.
|
||||
// * Cache priorities are less aggressively enforced. Unlike LRUCache, enough
|
||||
// transient LOW or BOTTOM priority items can evict HIGH priority entries that
|
||||
// are not referenced recently (or often) enough.
|
||||
// * If pinned entries leave little or nothing eligible for eviction,
|
||||
// performance can degrade substantially, because of clock eviction eating
|
||||
// CPU looking for evictable entries and because Release does not
|
||||
// pro-actively delete unreferenced entries when the cache is over-full.
|
||||
// Specifically, this makes this implementation more susceptible to the
|
||||
// following combination:
|
||||
// * num_shard_bits is high (e.g. 6)
|
||||
// * capacity small (e.g. some MBs)
|
||||
// * some large individual entries (e.g. non-partitioned filters)
|
||||
// where individual entries occupy a large portion of their shard capacity.
|
||||
// This should be mostly mitigated by the implementation picking a lower
|
||||
// number of cache shards than LRUCache for a given capacity (when
|
||||
// num_shard_bits is not overridden; see calls to GetDefaultCacheShardBits()).
|
||||
// * With strict_capacity_limit=false, respecting the capacity limit is not as
|
||||
// aggressive as LRUCache. The limit might be transiently exceeded by a very
|
||||
// small number of entries even when not strictly necessary, and slower to
|
||||
// recover after pinning forces limit to be substantially exceeded. (Even with
|
||||
// strict_capacity_limit=true, RocksDB will nevertheless transiently allocate
|
||||
// memory before discovering it is over the block cache capacity, so this
|
||||
// should not be a detectable regression in respecting memory limits, except
|
||||
// on exceptionally small caches.)
|
||||
// * In some cases, erased or duplicated entries might not be freed
|
||||
// immediately. They will eventually be freed by eviction from further Inserts.
|
||||
// * Internal metadata can overflow if the number of simultaneous references
|
||||
// to a cache handle reaches many millions.
|
||||
//
|
||||
// High-level eviction algorithm
|
||||
// -----------------------------
|
||||
// A score (or "countdown") is maintained for each entry, initially determined
|
||||
// by priority. The score is incremented on each Lookup, up to a max of 3,
|
||||
// though is easily returned to previous state if useful=false with Release.
|
||||
// During CLOCK-style eviction iteration, entries with score > 0 are
|
||||
// decremented if currently unreferenced and entries with score == 0 are
|
||||
// evicted if currently unreferenced. Note that scoring might not be perfect
|
||||
// because entries can be referenced transiently within the cache even when
|
||||
// there are no outside references to the entry.
|
||||
//
|
||||
// Cache sharding like LRUCache is used to reduce contention on usage+eviction
|
||||
// state, though here the performance improvement from more shards is small,
|
||||
// and (as noted above) potentially detrimental if shard capacity is too close
|
||||
// to largest entry size. Here cache sharding mostly only affects cache update
|
||||
// (Insert / Erase) performance, not read performance.
|
||||
//
|
||||
// Read efficiency (hot path)
|
||||
// --------------------------
|
||||
// Mostly to minimize the cost of accessing metadata blocks with
|
||||
// cache_index_and_filter_blocks=true, we focus on optimizing Lookup and
|
||||
// Release. In terms of concurrency, at a minimum, these operations have
|
||||
// to do reference counting (and Lookup has to compare full keys in a safe
|
||||
// way). Can we fold in all the other metadata tracking *for free* with
|
||||
// Lookup and Release doing a simple atomic fetch_add/fetch_sub? (Assume
|
||||
// for the moment that Lookup succeeds on the first probe.)
|
||||
//
|
||||
// We have a clever way of encoding an entry's reference count and countdown
|
||||
// clock so that Lookup and Release are each usually a single atomic addition.
|
||||
// In a single metadata word we have both an "acquire" count, incremented by
|
||||
// Lookup, and a "release" count, incremented by Release. If useful=false,
|
||||
// Release can instead decrement the acquire count. Thus the current ref
|
||||
// count is (acquires - releases), and the countdown clock is min(3, acquires).
|
||||
// Note that only unreferenced entries (acquires == releases) are eligible
|
||||
// for CLOCK manipulation and eviction. We tolerate use of more expensive
|
||||
// compare_exchange operations for cache writes (insertions and erasures).
|
||||
//
|
||||
// In a cache receiving many reads and little or no writes, it is possible
|
||||
// for the acquire and release counters to overflow. Assuming the *current*
|
||||
// refcount never reaches to many millions, we only have to correct for
|
||||
// overflow in both counters in Release, not in Lookup. The overflow check
|
||||
// should be only 1-2 CPU cycles per Release because it is a predictable
|
||||
// branch on a simple condition on data already in registers.
|
||||
//
|
||||
// Slot states
|
||||
// -----------
|
||||
// We encode a state indicator into the same metadata word with the
|
||||
// acquire and release counters. This allows bigger state transitions to
|
||||
// be atomic. States:
|
||||
//
|
||||
// * Empty - slot is not in use and unowned. All other metadata and data is
|
||||
// in an undefined state.
|
||||
// * Construction - slot is exclusively owned by one thread, the thread
|
||||
// successfully entering this state, for populating or freeing data.
|
||||
// * Shareable (group) - slot holds an entry with counted references for
|
||||
// pinning and reading, including
|
||||
// * Visible - slot holds an entry that can be returned by Lookup
|
||||
// * Invisible - slot holds an entry that is not visible to Lookup
|
||||
// (erased by user) but can be read by existing references, and ref count
|
||||
// changed by Ref and Release.
|
||||
//
|
||||
// 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:
|
||||
// Empty -> Construction (in Insert): The encoding of state enables Insert to
|
||||
// perform an optimistic atomic bitwise-or to take ownership if a slot is
|
||||
// empty, or otherwise make no state change.
|
||||
//
|
||||
// Construction -> Visible (in Insert): This can be a simple assignment to the
|
||||
// metadata word because the current thread has exclusive ownership and other
|
||||
// metadata is meaningless.
|
||||
//
|
||||
// Visible -> Invisible (in Erase): This can be a bitwise-and while holding
|
||||
// a shared reference, which is safe because the change is idempotent (in case
|
||||
// of parallel Erase). By the way, we never go Invisible->Visible.
|
||||
//
|
||||
// Shareable -> Construction (in Evict part of Insert, in Erase, and in
|
||||
// Release if Invisible): This is for starting to freeing/deleting an
|
||||
// unreferenced entry. We have to use compare_exchange to ensure we only make
|
||||
// this transition when there are zero refs.
|
||||
//
|
||||
// Construction -> Empty (in same places): This is for completing free/delete
|
||||
// of an entry. A "release" atomic store suffices, as we have exclusive
|
||||
// ownership of the slot but have to ensure none of the data member reads are
|
||||
// re-ordered after committing the state transition.
|
||||
//
|
||||
// Insert
|
||||
// ------
|
||||
// If Insert were to guarantee replacing an existing entry for a key, there
|
||||
// would be complications for concurrency and efficiency. First, consider how
|
||||
// many probes to get to an entry. To ensure Lookup never waits and
|
||||
// availability of a key is uninterrupted, we would need to use a different
|
||||
// slot for a new entry for the same key. This means it is most likely in a
|
||||
// later probing position than the old version, which should soon be removed.
|
||||
// (Also, an entry is too big to replace atomically, even if no current refs.)
|
||||
//
|
||||
// However, overwrite capability is not really needed by RocksDB. Also, we
|
||||
// know from our "redundant" stats that overwrites are very rare for the block
|
||||
// cache, so we should not spend much to make them effective.
|
||||
//
|
||||
// So instead we Insert as soon as we find an empty slot in the probing
|
||||
// sequence without seeing an existing (visible) entry for the same key. This
|
||||
// way we only insert if we can improve the probing performance, and we don't
|
||||
// need to probe beyond our insert position, assuming we are willing to let
|
||||
// the previous entry for the same key die of old age (eventual eviction from
|
||||
// not being used). We can reach a similar state with concurrent insertions,
|
||||
// where one will pass over the other while it is "under construction."
|
||||
// This temporary duplication is acceptable for RocksDB block cache because
|
||||
// we know redundant insertion is rare.
|
||||
//
|
||||
// Another problem to solve is what to return to the caller when we find an
|
||||
// existing entry whose probing position we cannot improve on, or when the
|
||||
// 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 "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.
|
||||
//
|
||||
// Usage on capacity
|
||||
// -----------------
|
||||
// Insert takes different approaches to usage tracking depending on
|
||||
// strict_capacity_limit setting. If true, we enforce a kind of strong
|
||||
// consistency where compare-exchange is used to ensure the usage number never
|
||||
// exceeds its limit, and provide threads with an authoritative signal on how
|
||||
// much "usage" they have taken ownership of. With strict_capacity_limit=false,
|
||||
// we use a kind of "eventual consistency" where all threads Inserting to the
|
||||
// same cache shard might race on reserving the same space, but the
|
||||
// over-commitment will be worked out in later insertions. It is kind of a
|
||||
// dance because we don't want threads racing each other too much on paying
|
||||
// down the over-commitment (with eviction) either.
|
||||
//
|
||||
// Eviction
|
||||
// --------
|
||||
// A key part of Insert is evicting some entries currently unreferenced to
|
||||
// make room for new entries. The high-level eviction algorithm is described
|
||||
// above, but the details are also interesting. A key part is parallelizing
|
||||
// eviction with a single CLOCK pointer. This works by each thread working on
|
||||
// eviction pre-emptively incrementing the CLOCK pointer, and then CLOCK-
|
||||
// updating or evicting the incremented-over slot(s). To reduce contention at
|
||||
// the cost of possibly evicting too much, each thread increments the clock
|
||||
// pointer by 4, so commits to updating at least 4 slots per batch. As
|
||||
// described above, a CLOCK update will decrement the "countdown" of
|
||||
// unreferenced entries, or evict unreferenced entries with zero countdown.
|
||||
// Referenced entries are not updated, because we (presumably) don't want
|
||||
// long-referenced entries to age while referenced. Note however that we
|
||||
// cannot distinguish transiently referenced entries from cache user
|
||||
// references, so some CLOCK updates might be somewhat arbitrarily skipped.
|
||||
// This is OK as long as it is rare enough that eviction order is still
|
||||
// pretty good.
|
||||
//
|
||||
// There is no synchronization on the completion of the CLOCK updates, so it
|
||||
// is theoretically possible for another thread to cycle back around and have
|
||||
// two threads racing on CLOCK updates to the same slot. Thus, we cannot rely
|
||||
// on any implied exclusivity to make the updates or eviction more efficient.
|
||||
// These updates use an opportunistic compare-exchange (no loop), where a
|
||||
// racing thread might cause the update to be skipped without retry, but in
|
||||
// such case the update is likely not needed because the most likely update
|
||||
// to an entry is that it has become referenced. (TODO: test efficiency of
|
||||
// avoiding compare-exchange loop)
|
||||
//
|
||||
// Release
|
||||
// -------
|
||||
// In the common case, Release is a simple atomic increment of the release
|
||||
// counter. There is a simple overflow check that only does another atomic
|
||||
// update in extremely rare cases, so costs almost nothing.
|
||||
//
|
||||
// If the Release specifies "not useful", we can instead decrement the
|
||||
// acquire counter, which returns to the same CLOCK state as before Lookup
|
||||
// or Ref.
|
||||
//
|
||||
// Adding a check for over-full cache on every release to zero-refs would
|
||||
// likely be somewhat expensive, increasing read contention on cache shard
|
||||
// metadata. Instead we are less aggressive about deleting entries right
|
||||
// away in those cases.
|
||||
//
|
||||
// However Release tries to immediately delete entries reaching zero refs
|
||||
// if (a) erase_if_last_ref is set by the caller, or (b) the entry is already
|
||||
// marked invisible. Both of these are checks on values already in CPU
|
||||
// registers so do not increase cross-CPU contention when not applicable.
|
||||
// When applicable, they use a compare-exchange loop to take exclusive
|
||||
// ownership of the slot for freeing the entry. These are rare cases
|
||||
// that should not usually affect performance.
|
||||
//
|
||||
// Erase
|
||||
// -----
|
||||
// Searches for an entry like Lookup but moves it to Invisible state if found.
|
||||
// This state transition is with bit operations so is idempotent and safely
|
||||
// done while only holding a shared "read" reference. Like Release, it makes
|
||||
// a best effort to immediately release an Invisible entry that reaches zero
|
||||
// refs, but there are some corner cases where it will only be freed by the
|
||||
// clock eviction process.
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
// 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;
|
||||
|
||||
using CacheKeyBytes = std::array<char, kCacheKeySize>;
|
||||
|
||||
struct ClockHandleBasicData {
|
||||
void* value = nullptr;
|
||||
Cache::DeleterFn deleter = nullptr;
|
||||
CacheKeyBytes key = {};
|
||||
size_t total_charge = 0;
|
||||
|
||||
Slice KeySlice() const { return Slice(key.data(), kCacheKeySize); }
|
||||
|
||||
void FreeData() const {
|
||||
if (deleter) {
|
||||
(*deleter)(KeySlice(), value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct ClockHandleMoreData : public ClockHandleBasicData {
|
||||
uint32_t hash = 0;
|
||||
};
|
||||
|
||||
// Target size to be exactly a common cache line size (see static_assert in
|
||||
// clock_cache.cc)
|
||||
struct ALIGN_AS(64U) ClockHandle : public ClockHandleMoreData {
|
||||
// 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 | state marker |
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// For reading or updating counters in meta word.
|
||||
static constexpr uint8_t kCounterNumBits = 30;
|
||||
static constexpr uint64_t kCounterMask = (uint64_t{1} << kCounterNumBits) - 1;
|
||||
|
||||
static constexpr uint8_t kAcquireCounterShift = 0;
|
||||
static constexpr uint64_t kAcquireIncrement = uint64_t{1}
|
||||
<< kAcquireCounterShift;
|
||||
static constexpr uint8_t kReleaseCounterShift = kCounterNumBits;
|
||||
static constexpr uint64_t kReleaseIncrement = uint64_t{1}
|
||||
<< kReleaseCounterShift;
|
||||
|
||||
// For reading or updating the state marker in meta word
|
||||
static constexpr uint8_t kStateShift = 2U * kCounterNumBits;
|
||||
|
||||
// Bits contribution to state marker.
|
||||
// Occupied means any state other than empty
|
||||
static constexpr uint8_t kStateOccupiedBit = 0b100;
|
||||
// Shareable means the entry is reference counted (visible or invisible)
|
||||
// (only set if also occupied)
|
||||
static constexpr uint8_t kStateShareableBit = 0b010;
|
||||
// Visible is only set if also shareable
|
||||
static constexpr uint8_t kStateVisibleBit = 0b001;
|
||||
|
||||
// Complete state markers (not shifted into full word)
|
||||
static constexpr uint8_t kStateEmpty = 0b000;
|
||||
static constexpr uint8_t kStateConstruction = kStateOccupiedBit;
|
||||
static constexpr uint8_t kStateInvisible =
|
||||
kStateOccupiedBit | kStateShareableBit;
|
||||
static constexpr uint8_t kStateVisible =
|
||||
kStateOccupiedBit | kStateShareableBit | kStateVisibleBit;
|
||||
|
||||
// Constants for initializing the countdown clock. (Countdown clock is only
|
||||
// in effect with zero refs, acquire counter == release counter, and in that
|
||||
// case the countdown clock == both of those counters.)
|
||||
static constexpr uint8_t kHighCountdown = 3;
|
||||
static constexpr uint8_t kLowCountdown = 2;
|
||||
static constexpr uint8_t kBottomCountdown = 1;
|
||||
// During clock update, treat any countdown clock value greater than this
|
||||
// value the same as this value.
|
||||
static constexpr uint8_t kMaxCountdown = kHighCountdown;
|
||||
// TODO: make these coundown values tuning parameters for eviction?
|
||||
|
||||
// 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 ClockHandleTable {
|
||||
public:
|
||||
explicit ClockHandleTable(int hash_bits, bool initial_charge_metadata);
|
||||
~ClockHandleTable();
|
||||
|
||||
Status Insert(const ClockHandleMoreData& proto, ClockHandle** handle,
|
||||
Cache::Priority priority, size_t capacity,
|
||||
bool strict_capacity_limit);
|
||||
|
||||
ClockHandle* Lookup(const CacheKeyBytes& key, uint32_t hash);
|
||||
|
||||
bool Release(ClockHandle* handle, bool useful, bool erase_if_last_ref);
|
||||
|
||||
void Ref(ClockHandle& handle);
|
||||
|
||||
void Erase(const CacheKeyBytes& key, uint32_t hash);
|
||||
|
||||
void ConstApplyToEntriesRange(std::function<void(const ClockHandle&)> func,
|
||||
uint32_t index_begin, uint32_t index_end,
|
||||
bool apply_if_will_be_deleted) const;
|
||||
|
||||
void EraseUnRefEntries();
|
||||
|
||||
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_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
size_t GetUsage() const { return usage_.load(std::memory_order_relaxed); }
|
||||
|
||||
size_t GetDetachedUsage() const {
|
||||
return detached_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(ClockHandle& handle, size_t n);
|
||||
void TEST_ReleaseN(ClockHandle* handle, size_t n);
|
||||
|
||||
private: // functions
|
||||
// Returns x mod 2^{length_bits_}.
|
||||
uint32_t ModTableSize(uint32_t x) { return x & length_bits_mask_; }
|
||||
|
||||
// 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,
|
||||
uint32_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(uint32_t hash,
|
||||
std::function<bool(ClockHandle*)> match,
|
||||
std::function<bool(ClockHandle*)> stop,
|
||||
std::function<void(ClockHandle*)> update,
|
||||
uint32_t& probe);
|
||||
|
||||
// Re-decrement all displacements in probe path starting from beginning
|
||||
// until (not including) the given handle
|
||||
void Rollback(uint32_t hash, const ClockHandle* h);
|
||||
|
||||
private: // data
|
||||
// Number of hash bits used for table index.
|
||||
// The size of the table is 1 << length_bits_.
|
||||
const int length_bits_;
|
||||
|
||||
// For faster computation of ModTableSize.
|
||||
const uint32_t length_bits_mask_;
|
||||
|
||||
// Maximum number of elements the user can store in the table.
|
||||
const uint32_t occupancy_limit_;
|
||||
|
||||
// Array of slots comprising the hash table.
|
||||
const std::unique_ptr<ClockHandle[]> array_;
|
||||
|
||||
// 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<uint32_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.
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) ClockCacheShard final : public CacheShard {
|
||||
public:
|
||||
ClockCacheShard(size_t capacity, size_t estimated_value_size,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
~ClockCacheShard() override = default;
|
||||
|
||||
// TODO: document limitations
|
||||
void SetCapacity(size_t capacity) override;
|
||||
|
||||
void SetStrictCapacityLimit(bool strict_capacity_limit) override;
|
||||
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
Cache::DeleterFn deleter, Cache::Handle** handle,
|
||||
Cache::Priority priority) override;
|
||||
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
|
||||
bool Release(Cache::Handle* handle, bool useful,
|
||||
bool erase_if_last_ref) override;
|
||||
|
||||
bool Release(Cache::Handle* handle, bool erase_if_last_ref = false) override;
|
||||
|
||||
bool Ref(Cache::Handle* handle) override;
|
||||
|
||||
void Erase(const Slice& key, uint32_t hash) override;
|
||||
|
||||
size_t GetUsage() const override;
|
||||
|
||||
size_t GetPinnedUsage() const override;
|
||||
|
||||
size_t GetOccupancyCount() const override;
|
||||
|
||||
size_t GetTableAddressCount() const override;
|
||||
|
||||
void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
uint32_t average_entries_per_lock, uint32_t* state) override;
|
||||
|
||||
void EraseUnRefEntries() override;
|
||||
|
||||
std::string GetPrintableOptions() const override { return std::string{}; }
|
||||
|
||||
// SecondaryCache not yet supported
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
Cache::Handle** handle, Cache::Priority priority) override {
|
||||
return Insert(key, hash, value, charge, helper->del_cb, handle, priority);
|
||||
}
|
||||
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
const Cache::CreateCallback& /*create_cb*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/,
|
||||
Statistics* /*stats*/) override {
|
||||
return Lookup(key, hash);
|
||||
}
|
||||
|
||||
bool IsReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
|
||||
void Wait(Cache::Handle* /*handle*/) override {}
|
||||
|
||||
// Acquire/release N references
|
||||
void TEST_RefN(Cache::Handle* handle, size_t n);
|
||||
void TEST_ReleaseN(Cache::Handle* handle, size_t n);
|
||||
|
||||
private: // functions
|
||||
friend class ClockCache;
|
||||
friend class ClockCacheTest;
|
||||
|
||||
ClockHandle* DetachedInsert(const ClockHandleMoreData& 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
|
||||
ClockHandleTable table_;
|
||||
|
||||
// Maximum total charge of all elements stored in the table.
|
||||
std::atomic<size_t> capacity_;
|
||||
|
||||
// Whether to reject insertion if cache reaches its full capacity.
|
||||
std::atomic<bool> strict_capacity_limit_;
|
||||
}; // class ClockCacheShard
|
||||
|
||||
class HyperClockCache
|
||||
#ifdef NDEBUG
|
||||
final
|
||||
#if defined(TBB) && !defined(ROCKSDB_LITE)
|
||||
#define SUPPORT_CLOCK_CACHE
|
||||
#endif
|
||||
: public ShardedCache {
|
||||
public:
|
||||
HyperClockCache(size_t capacity, size_t estimated_value_size,
|
||||
int num_shard_bits, bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata);
|
||||
|
||||
~HyperClockCache() override;
|
||||
|
||||
const char* Name() const override { return "HyperClockCache"; }
|
||||
|
||||
CacheShard* GetShard(uint32_t shard) override;
|
||||
|
||||
const CacheShard* GetShard(uint32_t shard) const override;
|
||||
|
||||
void* Value(Handle* handle) override;
|
||||
|
||||
size_t GetCharge(Handle* handle) const override;
|
||||
|
||||
uint32_t GetHash(Handle* handle) const override;
|
||||
|
||||
DeleterFn GetDeleter(Handle* handle) const override;
|
||||
|
||||
void DisownData() override;
|
||||
|
||||
private:
|
||||
ClockCacheShard* shards_ = nullptr;
|
||||
|
||||
int num_shards_;
|
||||
}; // class HyperClockCache
|
||||
|
||||
} // namespace hyper_clock_cache
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
-312
@@ -1,312 +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 "cache/compressed_secondary_cache.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
CompressedSecondaryCache::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, 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);
|
||||
}
|
||||
|
||||
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;
|
||||
is_in_sec_cache = false;
|
||||
Cache::Handle* lru_handle = cache_->Lookup(key);
|
||||
if (lru_handle == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* handle_value = cache_->Value(lru_handle);
|
||||
if (handle_value == nullptr) {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/false);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CacheAllocationPtr* ptr{nullptr};
|
||||
CacheAllocationPtr merged_value;
|
||||
size_t handle_value_charge{0};
|
||||
if (cache_options_.enable_custom_split_merge) {
|
||||
CacheValueChunk* value_chunk_ptr =
|
||||
reinterpret_cast<CacheValueChunk*>(handle_value);
|
||||
merged_value = MergeChunksIntoValue(value_chunk_ptr, handle_value_charge);
|
||||
ptr = &merged_value;
|
||||
} else {
|
||||
ptr = reinterpret_cast<CacheAllocationPtr*>(handle_value);
|
||||
handle_value_charge = cache_->GetCharge(lru_handle);
|
||||
}
|
||||
|
||||
Status s;
|
||||
void* value{nullptr};
|
||||
size_t charge{0};
|
||||
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,
|
||||
UncompressionDict::GetEmptyDict(),
|
||||
cache_options_.compression_type);
|
||||
|
||||
size_t uncompressed_size{0};
|
||||
CacheAllocationPtr uncompressed = UncompressData(
|
||||
uncompression_info, (char*)ptr->get(), handle_value_charge,
|
||||
&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 = create_cb(uncompressed.get(), uncompressed_size, &value, &charge);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (advise_erase) {
|
||||
cache_->Release(lru_handle, /*erase_if_last_ref=*/true);
|
||||
// Insert a dummy handle.
|
||||
cache_
|
||||
->Insert(key, /*value=*/nullptr, /*charge=*/0,
|
||||
GetDeletionCallback(cache_options_.enable_custom_split_merge))
|
||||
.PermitUncheckedError();
|
||||
} else {
|
||||
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, void* value,
|
||||
const Cache::CacheItemHelper* helper) {
|
||||
if (value == nullptr) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
|
||||
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);
|
||||
CacheAllocationPtr ptr =
|
||||
AllocateBlock(size, cache_options_.memory_allocator.get());
|
||||
|
||||
Status s = (*helper->saveto_cb)(value, 0, size, ptr.get());
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
Slice val(ptr.get(), size);
|
||||
|
||||
std::string compressed_val;
|
||||
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);
|
||||
uint64_t sample_for_compression{0};
|
||||
CompressionInfo compression_info(
|
||||
compression_opts, compression_context, CompressionDict::GetEmptyDict(),
|
||||
cache_options_.compression_type, sample_for_compression);
|
||||
|
||||
bool success =
|
||||
CompressData(val, compression_info,
|
||||
cache_options_.compress_format_version, &compressed_val);
|
||||
|
||||
if (!success) {
|
||||
return Status::Corruption("Error compressing value.");
|
||||
}
|
||||
|
||||
val = Slice(compressed_val);
|
||||
size = compressed_val.size();
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_compressed_bytes, size);
|
||||
|
||||
if (!cache_options_.enable_custom_split_merge) {
|
||||
ptr = AllocateBlock(size, cache_options_.memory_allocator.get());
|
||||
memcpy(ptr.get(), compressed_val.data(), size);
|
||||
}
|
||||
}
|
||||
|
||||
PERF_COUNTER_ADD(compressed_sec_cache_insert_real_count, 1);
|
||||
if (cache_options_.enable_custom_split_merge) {
|
||||
size_t charge{0};
|
||||
CacheValueChunk* value_chunks_head =
|
||||
SplitValueIntoChunks(val, cache_options_.compression_type, charge);
|
||||
return cache_->Insert(key, value_chunks_head, charge, del_cb);
|
||||
} else {
|
||||
CacheAllocationPtr* buf = new CacheAllocationPtr(std::move(ptr));
|
||||
return cache_->Insert(key, buf, size, del_cb);
|
||||
}
|
||||
}
|
||||
|
||||
void CompressedSecondaryCache::Erase(const Slice& key) { cache_->Erase(key); }
|
||||
|
||||
std::string CompressedSecondaryCache::GetPrintableOptions() const {
|
||||
std::string ret;
|
||||
ret.reserve(20000);
|
||||
const int kBufferSize{200};
|
||||
char buffer[kBufferSize];
|
||||
ret.append(cache_->GetPrintableOptions());
|
||||
snprintf(buffer, kBufferSize, " compression_type : %s\n",
|
||||
CompressionTypeToString(cache_options_.compression_type).c_str());
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " compress_format_version : %d\n",
|
||||
cache_options_.compress_format_version);
|
||||
ret.append(buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
CompressedSecondaryCache::CacheValueChunk*
|
||||
CompressedSecondaryCache::SplitValueIntoChunks(
|
||||
const Slice& value, const CompressionType compression_type,
|
||||
size_t& charge) {
|
||||
assert(!value.empty());
|
||||
const char* src_ptr = value.data();
|
||||
size_t src_size{value.size()};
|
||||
|
||||
CacheValueChunk dummy_head = CacheValueChunk();
|
||||
CacheValueChunk* current_chunk = &dummy_head;
|
||||
// Do not split when value size is large or there is no compression.
|
||||
size_t predicted_chunk_size{0};
|
||||
size_t actual_chunk_size{0};
|
||||
size_t tmp_size{0};
|
||||
while (src_size > 0) {
|
||||
predicted_chunk_size = sizeof(CacheValueChunk) - 1 + src_size;
|
||||
auto upper =
|
||||
std::upper_bound(malloc_bin_sizes_.begin(), malloc_bin_sizes_.end(),
|
||||
predicted_chunk_size);
|
||||
// Do not split when value size is too small, too large, close to a bin
|
||||
// size, or there is no compression.
|
||||
if (upper == malloc_bin_sizes_.begin() ||
|
||||
upper == malloc_bin_sizes_.end() ||
|
||||
*upper - predicted_chunk_size < malloc_bin_sizes_.front() ||
|
||||
compression_type == kNoCompression) {
|
||||
tmp_size = predicted_chunk_size;
|
||||
} else {
|
||||
tmp_size = *(--upper);
|
||||
}
|
||||
|
||||
CacheValueChunk* new_chunk =
|
||||
reinterpret_cast<CacheValueChunk*>(new char[tmp_size]);
|
||||
current_chunk->next = new_chunk;
|
||||
current_chunk = current_chunk->next;
|
||||
actual_chunk_size = tmp_size - sizeof(CacheValueChunk) + 1;
|
||||
memcpy(current_chunk->data, src_ptr, actual_chunk_size);
|
||||
current_chunk->size = actual_chunk_size;
|
||||
src_ptr += actual_chunk_size;
|
||||
src_size -= actual_chunk_size;
|
||||
charge += tmp_size;
|
||||
}
|
||||
current_chunk->next = nullptr;
|
||||
|
||||
return dummy_head.next;
|
||||
}
|
||||
|
||||
CacheAllocationPtr CompressedSecondaryCache::MergeChunksIntoValue(
|
||||
const void* chunks_head, size_t& charge) {
|
||||
const CacheValueChunk* head =
|
||||
reinterpret_cast<const CacheValueChunk*>(chunks_head);
|
||||
const CacheValueChunk* current_chunk = head;
|
||||
charge = 0;
|
||||
while (current_chunk != nullptr) {
|
||||
charge += current_chunk->size;
|
||||
current_chunk = current_chunk->next;
|
||||
}
|
||||
|
||||
CacheAllocationPtr ptr =
|
||||
AllocateBlock(charge, cache_options_.memory_allocator.get());
|
||||
current_chunk = head;
|
||||
size_t pos{0};
|
||||
while (current_chunk != nullptr) {
|
||||
memcpy(ptr.get() + pos, current_chunk->data, current_chunk->size);
|
||||
pos += current_chunk->size;
|
||||
current_chunk = current_chunk->next;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
Cache::DeleterFn CompressedSecondaryCache::GetDeletionCallback(
|
||||
bool enable_custom_split_merge) {
|
||||
if (enable_custom_split_merge) {
|
||||
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 {
|
||||
return [](const Slice& /*key*/, void* obj) {
|
||||
delete reinterpret_cast<CacheAllocationPtr*>(obj);
|
||||
obj = nullptr;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
-133
@@ -1,133 +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).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/compression.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CompressedSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
|
||||
public:
|
||||
CompressedSecondaryCacheResultHandle(void* value, size_t size)
|
||||
: value_(value), size_(size) {}
|
||||
virtual ~CompressedSecondaryCacheResultHandle() override = default;
|
||||
|
||||
CompressedSecondaryCacheResultHandle(
|
||||
const CompressedSecondaryCacheResultHandle&) = delete;
|
||||
CompressedSecondaryCacheResultHandle& operator=(
|
||||
const CompressedSecondaryCacheResultHandle&) = delete;
|
||||
|
||||
bool IsReady() override { return true; }
|
||||
|
||||
void Wait() override {}
|
||||
|
||||
void* Value() override { return value_; }
|
||||
|
||||
size_t Size() override { return size_; }
|
||||
|
||||
private:
|
||||
void* value_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
// The CompressedSecondaryCache is a concrete implementation of
|
||||
// rocksdb::SecondaryCache.
|
||||
//
|
||||
// When a block is found from CompressedSecondaryCache::Lookup, we check whether
|
||||
// there is a dummy block with the same key in the primary cache.
|
||||
// 1. If the dummy block exits, we erase the block from
|
||||
// CompressedSecondaryCache and insert it into the primary cache.
|
||||
// 2. If not, we just insert a dummy block into the primary cache
|
||||
// (charging the actual size of the block) and don not erase the block from
|
||||
// CompressedSecondaryCache. A standalone handle is returned to the caller.
|
||||
//
|
||||
// When a block is evicted from the primary cache, we check whether
|
||||
// there is a dummy block with the same key in CompressedSecondaryCache.
|
||||
// 1. If the dummy block exits, the block is inserted into
|
||||
// CompressedSecondaryCache.
|
||||
// 2. If not, we just insert a dummy block (size 0) in CompressedSecondaryCache.
|
||||
//
|
||||
// Users can also cast a pointer to CompressedSecondaryCache and call methods on
|
||||
// it directly, especially custom methods that may be added
|
||||
// in the future. For example -
|
||||
// std::unique_ptr<rocksdb::SecondaryCache> cache =
|
||||
// NewCompressedSecondaryCache(opts);
|
||||
// static_cast<CompressedSecondaryCache*>(cache.get())->Erase(key);
|
||||
|
||||
class CompressedSecondaryCache : public SecondaryCache {
|
||||
public:
|
||||
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);
|
||||
virtual ~CompressedSecondaryCache() override;
|
||||
|
||||
const char* Name() const override { return "CompressedSecondaryCache"; }
|
||||
|
||||
Status Insert(const Slice& key, void* value,
|
||||
const Cache::CacheItemHelper* helper) override;
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
|
||||
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; }
|
||||
|
||||
void Erase(const Slice& key) override;
|
||||
|
||||
void WaitAll(std::vector<SecondaryCacheResultHandle*> /*handles*/) override {}
|
||||
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
private:
|
||||
friend class CompressedSecondaryCacheTest;
|
||||
static constexpr std::array<uint16_t, 8> malloc_bin_sizes_{
|
||||
128, 256, 512, 1024, 2048, 4096, 8192, 16384};
|
||||
|
||||
struct CacheValueChunk {
|
||||
// TODO try "CacheAllocationPtr next;".
|
||||
CacheValueChunk* next;
|
||||
size_t size;
|
||||
// Beginning of the chunk data (MUST BE THE LAST FIELD IN THIS STRUCT!)
|
||||
char data[1];
|
||||
|
||||
void Free() { delete[] reinterpret_cast<char*>(this); }
|
||||
};
|
||||
|
||||
// Split value into chunks to better fit into jemalloc bins. The chunks
|
||||
// are stored in CacheValueChunk and extra charge is needed for each chunk,
|
||||
// so the cache charge is recalculated here.
|
||||
CacheValueChunk* SplitValueIntoChunks(const Slice& value,
|
||||
const CompressionType compression_type,
|
||||
size_t& charge);
|
||||
|
||||
// After merging chunks, the extra charge for each chunk is removed, so
|
||||
// the charge is recalculated.
|
||||
CacheAllocationPtr MergeChunksIntoValue(const void* chunks_head,
|
||||
size_t& charge);
|
||||
|
||||
// An implementation of Cache::DeleterFn.
|
||||
static Cache::DeleterFn GetDeletionCallback(bool enable_custom_split_merge);
|
||||
std::shared_ptr<Cache> cache_;
|
||||
CompressedSecondaryCacheOptions cache_options_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
-962
@@ -1,962 +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 "cache/compressed_secondary_cache.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
#include "memory/jemalloc_nodump_allocator.h"
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/random.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CompressedSecondaryCacheTest : public testing::Test {
|
||||
public:
|
||||
CompressedSecondaryCacheTest() : fail_create_(false) {}
|
||||
~CompressedSecondaryCacheTest() {}
|
||||
|
||||
protected:
|
||||
class TestItem {
|
||||
public:
|
||||
TestItem(const char* buf, size_t size) : buf_(new char[size]), size_(size) {
|
||||
memcpy(buf_.get(), buf, size);
|
||||
}
|
||||
~TestItem() {}
|
||||
|
||||
char* Buf() { return buf_.get(); }
|
||||
size_t Size() { return size_; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<char[]> buf_;
|
||||
size_t size_;
|
||||
};
|
||||
|
||||
static size_t SizeCallback(void* obj) {
|
||||
return reinterpret_cast<TestItem*>(obj)->Size();
|
||||
}
|
||||
|
||||
static Status SaveToCallback(void* from_obj, size_t from_offset,
|
||||
size_t length, void* out) {
|
||||
TestItem* item = reinterpret_cast<TestItem*>(from_obj);
|
||||
const char* buf = item->Buf();
|
||||
EXPECT_EQ(length, item->Size());
|
||||
EXPECT_EQ(from_offset, 0);
|
||||
memcpy(out, buf, length);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
static void DeletionCallback(const Slice& /*key*/, void* obj) {
|
||||
delete reinterpret_cast<TestItem*>(obj);
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
static Cache::CacheItemHelper helper_;
|
||||
|
||||
static Status SaveToCallbackFail(void* /*obj*/, size_t /*offset*/,
|
||||
size_t /*size*/, void* /*out*/) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
|
||||
static Cache::CacheItemHelper helper_fail_;
|
||||
|
||||
Cache::CreateCallback test_item_creator = [&](const void* buf, size_t size,
|
||||
void** out_obj,
|
||||
size_t* charge) -> Status {
|
||||
if (fail_create_) {
|
||||
return Status::NotSupported();
|
||||
}
|
||||
*out_obj = reinterpret_cast<void*>(new TestItem((char*)buf, size));
|
||||
*charge = size;
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
void SetFailCreate(bool fail) { fail_create_ = fail; }
|
||||
|
||||
void BasicTestHelper(std::shared_ptr<SecondaryCache> sec_cache,
|
||||
bool sec_cache_is_compressed) {
|
||||
get_perf_context()->Reset();
|
||||
bool is_in_sec_cache{true};
|
||||
// Lookup an non-existent key.
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle0 = sec_cache->Lookup(
|
||||
"k0", test_item_creator, true, /*advise_erase=*/true, is_in_sec_cache);
|
||||
ASSERT_EQ(handle0, nullptr);
|
||||
|
||||
Random rnd(301);
|
||||
// Insert and Lookup the item k1 for the first time.
|
||||
std::string str1(rnd.RandomString(1000));
|
||||
TestItem item1(str1.data(), str1.length());
|
||||
// A dummy handle is inserted if the item is inserted for the first time.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 = sec_cache->Lookup(
|
||||
"k1", test_item_creator, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
ASSERT_EQ(handle1_1, nullptr);
|
||||
|
||||
// Insert and Lookup the item k1 for the second time and advise erasing it.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 = sec_cache->Lookup(
|
||||
"k1", test_item_creator, true, /*advise_erase=*/true, is_in_sec_cache);
|
||||
ASSERT_NE(handle1_2, nullptr);
|
||||
ASSERT_FALSE(is_in_sec_cache);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
1000);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
1007);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<TestItem> val1 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle1_2->Value()));
|
||||
ASSERT_NE(val1, nullptr);
|
||||
ASSERT_EQ(memcmp(val1->Buf(), item1.Buf(), item1.Size()), 0);
|
||||
|
||||
// Lookup the item k1 again.
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_3 = sec_cache->Lookup(
|
||||
"k1", test_item_creator, true, /*advise_erase=*/true, is_in_sec_cache);
|
||||
ASSERT_EQ(handle1_3, nullptr);
|
||||
|
||||
// Insert and Lookup the item k2.
|
||||
std::string str2(rnd.RandomString(1000));
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
ASSERT_OK(sec_cache->Insert("k2", &item2,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 = sec_cache->Lookup(
|
||||
"k2", test_item_creator, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
ASSERT_EQ(handle2_1, nullptr);
|
||||
|
||||
ASSERT_OK(sec_cache->Insert("k2", &item2,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
2000);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
2014);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
}
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_2 = sec_cache->Lookup(
|
||||
"k2", test_item_creator, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
ASSERT_NE(handle2_2, nullptr);
|
||||
std::unique_ptr<TestItem> val2 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2_2->Value()));
|
||||
ASSERT_NE(val2, nullptr);
|
||||
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
|
||||
|
||||
std::vector<SecondaryCacheResultHandle*> handles = {handle1_2.get(),
|
||||
handle2_2.get()};
|
||||
sec_cache->WaitAll(handles);
|
||||
|
||||
sec_cache.reset();
|
||||
}
|
||||
|
||||
void BasicTest(bool sec_cache_is_compressed, bool use_jemalloc) {
|
||||
CompressedSecondaryCacheOptions opts;
|
||||
opts.capacity = 2048;
|
||||
opts.num_shard_bits = 0;
|
||||
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
opts.compression_type = CompressionType::kNoCompression;
|
||||
sec_cache_is_compressed = false;
|
||||
}
|
||||
} else {
|
||||
opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
if (use_jemalloc) {
|
||||
JemallocAllocatorOptions jopts;
|
||||
std::shared_ptr<MemoryAllocator> allocator;
|
||||
std::string msg;
|
||||
if (JemallocNodumpAllocator::IsSupported(&msg)) {
|
||||
Status s = NewJemallocNodumpAllocator(jopts, &allocator);
|
||||
if (s.ok()) {
|
||||
opts.memory_allocator = allocator;
|
||||
}
|
||||
} else {
|
||||
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
||||
}
|
||||
}
|
||||
std::shared_ptr<SecondaryCache> sec_cache =
|
||||
NewCompressedSecondaryCache(opts);
|
||||
|
||||
BasicTestHelper(sec_cache, sec_cache_is_compressed);
|
||||
}
|
||||
|
||||
void FailsTest(bool sec_cache_is_compressed) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
} else {
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
secondary_cache_opts.capacity = 1100;
|
||||
secondary_cache_opts.num_shard_bits = 0;
|
||||
std::shared_ptr<SecondaryCache> sec_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
// Insert and Lookup the first item.
|
||||
Random rnd(301);
|
||||
std::string str1(rnd.RandomString(1000));
|
||||
TestItem item1(str1.data(), str1.length());
|
||||
// Insert a dummy handle.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
// Insert k1.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
|
||||
// Insert and Lookup the second item.
|
||||
std::string str2(rnd.RandomString(200));
|
||||
TestItem item2(str2.data(), str2.length());
|
||||
// Insert a dummy handle, k1 is not evicted.
|
||||
ASSERT_OK(sec_cache->Insert("k2", &item2,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
bool is_in_sec_cache{false};
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1 = sec_cache->Lookup(
|
||||
"k1", test_item_creator, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
ASSERT_EQ(handle1, nullptr);
|
||||
|
||||
// Insert k2 and k1 is evicted.
|
||||
ASSERT_OK(sec_cache->Insert("k2", &item2,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2 = sec_cache->Lookup(
|
||||
"k2", test_item_creator, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
std::unique_ptr<TestItem> val2 =
|
||||
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2->Value()));
|
||||
ASSERT_NE(val2, nullptr);
|
||||
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
|
||||
|
||||
// Insert k1 again and a dummy handle is inserted.
|
||||
ASSERT_OK(sec_cache->Insert("k1", &item1,
|
||||
&CompressedSecondaryCacheTest::helper_));
|
||||
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 = sec_cache->Lookup(
|
||||
"k1", test_item_creator, true, /*advise_erase=*/false, is_in_sec_cache);
|
||||
ASSERT_EQ(handle1_1, nullptr);
|
||||
|
||||
// Create Fails.
|
||||
SetFailCreate(true);
|
||||
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 = sec_cache->Lookup(
|
||||
"k2", test_item_creator, true, /*advise_erase=*/true, is_in_sec_cache);
|
||||
ASSERT_EQ(handle2_1, nullptr);
|
||||
|
||||
// Save Fails.
|
||||
std::string str3 = rnd.RandomString(10);
|
||||
TestItem item3(str3.data(), str3.length());
|
||||
// The Status is OK because a dummy handle is inserted.
|
||||
ASSERT_OK(sec_cache->Insert("k3", &item3,
|
||||
&CompressedSecondaryCacheTest::helper_fail_));
|
||||
ASSERT_NOK(sec_cache->Insert("k3", &item3,
|
||||
&CompressedSecondaryCacheTest::helper_fail_));
|
||||
|
||||
sec_cache.reset();
|
||||
}
|
||||
|
||||
void BasicIntegrationTest(bool sec_cache_is_compressed,
|
||||
bool enable_custom_split_merge) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
sec_cache_is_compressed = false;
|
||||
}
|
||||
} else {
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
secondary_cache_opts.capacity = 6000;
|
||||
secondary_cache_opts.num_shard_bits = 0;
|
||||
secondary_cache_opts.enable_custom_split_merge = enable_custom_split_merge;
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
LRUCacheOptions lru_cache_opts(
|
||||
/*_capacity =*/1300, /*_num_shard_bits =*/0,
|
||||
/*_strict_capacity_limit =*/false, /*_high_pri_pool_ratio =*/0.5,
|
||||
/*_memory_allocator =*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio =*/0.0);
|
||||
lru_cache_opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lru_cache_opts);
|
||||
std::shared_ptr<Statistics> stats = CreateDBStatistics();
|
||||
|
||||
get_perf_context()->Reset();
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
TestItem* item1_1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k1", item1_1, &CompressedSecondaryCacheTest::helper_, str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1012);
|
||||
TestItem* item2_1 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's dummy item.
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k2", item2_1, &CompressedSecondaryCacheTest::helper_, str2.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
|
||||
std::string str3 = rnd.RandomString(1024);
|
||||
TestItem* item3_1 = new TestItem(str3.data(), str3.length());
|
||||
// After this Insert, primary cache contains k3 and secondary cache contains
|
||||
// k1's dummy item and k2's dummy item.
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k3", item3_1, &CompressedSecondaryCacheTest::helper_, str3.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
||||
|
||||
// After this Insert, primary cache contains k1 and secondary cache contains
|
||||
// k1's dummy item, k2's dummy item, and k3's dummy item.
|
||||
TestItem* item1_2 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k1", item1_2, &CompressedSecondaryCacheTest::helper_, str1.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3);
|
||||
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's item, k2's dummy item, and k3's dummy item.
|
||||
TestItem* item2_2 = new TestItem(str2.data(), str2.length());
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k2", item2_2, &CompressedSecondaryCacheTest::helper_, str2.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
str1.length());
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
1008);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
}
|
||||
|
||||
// After this Insert, primary cache contains k3 and secondary cache contains
|
||||
// k1's item and k2's item.
|
||||
TestItem* item3_2 = new TestItem(str3.data(), str3.length());
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k3", item3_2, &CompressedSecondaryCacheTest::helper_, str3.length()));
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
||||
if (sec_cache_is_compressed) {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
||||
str1.length() + str2.length());
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
||||
2027);
|
||||
} else {
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
||||
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
||||
}
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup("k3", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true,
|
||||
stats.get());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
TestItem* val3 = static_cast<TestItem*>(cache->Value(handle));
|
||||
ASSERT_NE(val3, nullptr);
|
||||
ASSERT_EQ(memcmp(val3->Buf(), item3_2->Buf(), item3_2->Size()), 0);
|
||||
cache->Release(handle);
|
||||
|
||||
// Lookup an non-existent key.
|
||||
handle = cache->Lookup("k0", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true,
|
||||
stats.get());
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
|
||||
// This Lookup should just insert a dummy handle in the primary cache
|
||||
// and the k1 is still in the secondary cache.
|
||||
handle = cache->Lookup("k1", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true,
|
||||
stats.get());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 1);
|
||||
TestItem* val1_1 = static_cast<TestItem*>(cache->Value(handle));
|
||||
ASSERT_NE(val1_1, nullptr);
|
||||
ASSERT_EQ(memcmp(val1_1->Buf(), str1.data(), str1.size()), 0);
|
||||
cache->Release(handle);
|
||||
|
||||
// This Lookup should erase k1 from the secondary cache and insert
|
||||
// it into primary cache; then k3 is demoted.
|
||||
handle = cache->Lookup("k1", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true,
|
||||
stats.get());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 1);
|
||||
cache->Release(handle);
|
||||
|
||||
// k2 is still in secondary cache.
|
||||
handle = cache->Lookup("k2", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true,
|
||||
stats.get());
|
||||
ASSERT_NE(handle, nullptr);
|
||||
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 2);
|
||||
cache->Release(handle);
|
||||
|
||||
cache.reset();
|
||||
secondary_cache.reset();
|
||||
}
|
||||
|
||||
void BasicIntegrationFailTest(bool sec_cache_is_compressed) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
} else {
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
secondary_cache_opts.capacity = 6000;
|
||||
secondary_cache_opts.num_shard_bits = 0;
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
LRUCacheOptions opts(
|
||||
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
||||
/*_strict_capacity_limit=*/false, /*_high_pri_pool_ratio=*/0.5,
|
||||
/*_memory_allocator=*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio=*/0.0);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
auto item1 =
|
||||
std::unique_ptr<TestItem>(new TestItem(str1.data(), str1.length()));
|
||||
ASSERT_NOK(cache->Insert("k1", item1.get(), nullptr, str1.length()));
|
||||
ASSERT_OK(cache->Insert("k1", item1.get(),
|
||||
&CompressedSecondaryCacheTest::helper_,
|
||||
str1.length()));
|
||||
item1.release(); // Appease clang-analyze "potential memory leak"
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup("k2", nullptr, test_item_creator,
|
||||
Cache::Priority::LOW, true);
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
handle = cache->Lookup("k2", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, false);
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
|
||||
cache.reset();
|
||||
secondary_cache.reset();
|
||||
}
|
||||
|
||||
void IntegrationSaveFailTest(bool sec_cache_is_compressed) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
} else {
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
secondary_cache_opts.capacity = 6000;
|
||||
secondary_cache_opts.num_shard_bits = 0;
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
LRUCacheOptions opts(
|
||||
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
||||
/*_strict_capacity_limit=*/false, /*_high_pri_pool_ratio=*/0.5,
|
||||
/*_memory_allocator=*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio=*/0.0);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
TestItem* item1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1,
|
||||
&CompressedSecondaryCacheTest::helper_fail_,
|
||||
str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1002);
|
||||
TestItem* item2 = new TestItem(str2.data(), str2.length());
|
||||
// k1 should be demoted to the secondary cache.
|
||||
ASSERT_OK(cache->Insert("k2", item2,
|
||||
&CompressedSecondaryCacheTest::helper_fail_,
|
||||
str2.length()));
|
||||
|
||||
Cache::Handle* handle;
|
||||
handle = cache->Lookup("k2", &CompressedSecondaryCacheTest::helper_fail_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
// This lookup should fail, since k1 demotion would have failed.
|
||||
handle = cache->Lookup("k1", &CompressedSecondaryCacheTest::helper_fail_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
// Since k1 was not promoted, k2 should still be in cache.
|
||||
handle = cache->Lookup("k2", &CompressedSecondaryCacheTest::helper_fail_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
|
||||
cache.reset();
|
||||
secondary_cache.reset();
|
||||
}
|
||||
|
||||
void IntegrationCreateFailTest(bool sec_cache_is_compressed) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
} else {
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
secondary_cache_opts.capacity = 6000;
|
||||
secondary_cache_opts.num_shard_bits = 0;
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
LRUCacheOptions opts(
|
||||
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
||||
/*_strict_capacity_limit=*/false, /*_high_pri_pool_ratio=*/0.5,
|
||||
/*_memory_allocator=*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio=*/0.0);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
TestItem* item1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert("k1", item1, &CompressedSecondaryCacheTest::helper_,
|
||||
str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1002);
|
||||
TestItem* item2 = new TestItem(str2.data(), str2.length());
|
||||
// k1 should be demoted to the secondary cache.
|
||||
ASSERT_OK(cache->Insert("k2", item2, &CompressedSecondaryCacheTest::helper_,
|
||||
str2.length()));
|
||||
|
||||
Cache::Handle* handle;
|
||||
SetFailCreate(true);
|
||||
handle = cache->Lookup("k2", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
// This lookup should fail, since k1 creation would have failed
|
||||
handle = cache->Lookup("k1", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_EQ(handle, nullptr);
|
||||
// Since k1 didn't get promoted, k2 should still be in cache
|
||||
handle = cache->Lookup("k2", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle, nullptr);
|
||||
cache->Release(handle);
|
||||
|
||||
cache.reset();
|
||||
secondary_cache.reset();
|
||||
}
|
||||
|
||||
void IntegrationFullCapacityTest(bool sec_cache_is_compressed) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
|
||||
if (sec_cache_is_compressed) {
|
||||
if (!LZ4_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
} else {
|
||||
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
||||
}
|
||||
|
||||
secondary_cache_opts.capacity = 6000;
|
||||
secondary_cache_opts.num_shard_bits = 0;
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
LRUCacheOptions opts(
|
||||
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
||||
/*_strict_capacity_limit=*/false, /*_high_pri_pool_ratio=*/0.5,
|
||||
/*_memory_allocator=*/nullptr, kDefaultToAdaptiveMutex,
|
||||
kDefaultCacheMetadataChargePolicy, /*_low_pri_pool_ratio=*/0.0);
|
||||
opts.secondary_cache = secondary_cache;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(opts);
|
||||
|
||||
Random rnd(301);
|
||||
std::string str1 = rnd.RandomString(1001);
|
||||
TestItem* item1_1 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k1", item1_1, &CompressedSecondaryCacheTest::helper_, str1.length()));
|
||||
|
||||
std::string str2 = rnd.RandomString(1002);
|
||||
std::string str2_clone{str2};
|
||||
TestItem* item2 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's dummy item.
|
||||
ASSERT_OK(cache->Insert("k2", item2, &CompressedSecondaryCacheTest::helper_,
|
||||
str2.length()));
|
||||
|
||||
// After this Insert, primary cache contains k1 and secondary cache contains
|
||||
// k1's dummy item and k2's dummy item.
|
||||
TestItem* item1_2 = new TestItem(str1.data(), str1.length());
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k1", item1_2, &CompressedSecondaryCacheTest::helper_, str1.length()));
|
||||
|
||||
TestItem* item2_2 = new TestItem(str2.data(), str2.length());
|
||||
// After this Insert, primary cache contains k2 and secondary cache contains
|
||||
// k1's item and k2's dummy item.
|
||||
ASSERT_OK(cache->Insert(
|
||||
"k2", item2_2, &CompressedSecondaryCacheTest::helper_, str2.length()));
|
||||
|
||||
Cache::Handle* handle2;
|
||||
handle2 = cache->Lookup("k2", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
cache->Release(handle2);
|
||||
|
||||
// k1 promotion should fail because cache is at capacity and
|
||||
// strict_capacity_limit is true, but the lookup should still succeed.
|
||||
// A k1's dummy item is inserted into primary cache.
|
||||
Cache::Handle* handle1;
|
||||
handle1 = cache->Lookup("k1", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle1, nullptr);
|
||||
cache->Release(handle1);
|
||||
|
||||
// Since k1 didn't get inserted, k2 should still be in cache
|
||||
handle2 = cache->Lookup("k2", &CompressedSecondaryCacheTest::helper_,
|
||||
test_item_creator, Cache::Priority::LOW, true);
|
||||
ASSERT_NE(handle2, nullptr);
|
||||
cache->Release(handle2);
|
||||
|
||||
cache.reset();
|
||||
secondary_cache.reset();
|
||||
}
|
||||
|
||||
void SplitValueIntoChunksTest() {
|
||||
JemallocAllocatorOptions jopts;
|
||||
std::shared_ptr<MemoryAllocator> allocator;
|
||||
std::string msg;
|
||||
if (JemallocNodumpAllocator::IsSupported(&msg)) {
|
||||
Status s = NewJemallocNodumpAllocator(jopts, &allocator);
|
||||
if (!s.ok()) {
|
||||
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
||||
}
|
||||
} else {
|
||||
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
||||
}
|
||||
|
||||
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0,
|
||||
allocator);
|
||||
Random rnd(301);
|
||||
// 8500 = 8169 + 233 + 98, so there should be 3 chunks after split.
|
||||
size_t str_size{8500};
|
||||
std::string str = rnd.RandomString(static_cast<int>(str_size));
|
||||
size_t charge{0};
|
||||
CacheValueChunk* chunks_head =
|
||||
sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge);
|
||||
ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1));
|
||||
|
||||
CacheValueChunk* current_chunk = chunks_head;
|
||||
ASSERT_EQ(current_chunk->size, 8192 - sizeof(CacheValueChunk) + 1);
|
||||
current_chunk = current_chunk->next;
|
||||
ASSERT_EQ(current_chunk->size, 256 - sizeof(CacheValueChunk) + 1);
|
||||
current_chunk = current_chunk->next;
|
||||
ASSERT_EQ(current_chunk->size, 98);
|
||||
|
||||
sec_cache->GetDeletionCallback(true)("dummy", chunks_head);
|
||||
}
|
||||
|
||||
void MergeChunksIntoValueTest() {
|
||||
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
||||
Random rnd(301);
|
||||
size_t size1{2048};
|
||||
std::string str1 = rnd.RandomString(static_cast<int>(size1));
|
||||
CacheValueChunk* current_chunk = reinterpret_cast<CacheValueChunk*>(
|
||||
new char[sizeof(CacheValueChunk) - 1 + size1]);
|
||||
CacheValueChunk* chunks_head = current_chunk;
|
||||
memcpy(current_chunk->data, str1.data(), size1);
|
||||
current_chunk->size = size1;
|
||||
|
||||
size_t size2{256};
|
||||
std::string str2 = rnd.RandomString(static_cast<int>(size2));
|
||||
current_chunk->next = reinterpret_cast<CacheValueChunk*>(
|
||||
new char[sizeof(CacheValueChunk) - 1 + size2]);
|
||||
current_chunk = current_chunk->next;
|
||||
memcpy(current_chunk->data, str2.data(), size2);
|
||||
current_chunk->size = size2;
|
||||
|
||||
size_t size3{31};
|
||||
std::string str3 = rnd.RandomString(static_cast<int>(size3));
|
||||
current_chunk->next = reinterpret_cast<CacheValueChunk*>(
|
||||
new char[sizeof(CacheValueChunk) - 1 + size3]);
|
||||
current_chunk = current_chunk->next;
|
||||
memcpy(current_chunk->data, str3.data(), size3);
|
||||
current_chunk->size = size3;
|
||||
current_chunk->next = nullptr;
|
||||
|
||||
std::string str = str1 + str2 + str3;
|
||||
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0);
|
||||
size_t charge{0};
|
||||
CacheAllocationPtr value =
|
||||
sec_cache->MergeChunksIntoValue(chunks_head, charge);
|
||||
ASSERT_EQ(charge, size1 + size2 + size3);
|
||||
std::string value_str{value.get(), charge};
|
||||
ASSERT_EQ(strcmp(value_str.data(), str.data()), 0);
|
||||
|
||||
while (chunks_head != nullptr) {
|
||||
CacheValueChunk* tmp_chunk = chunks_head;
|
||||
chunks_head = chunks_head->next;
|
||||
tmp_chunk->Free();
|
||||
}
|
||||
}
|
||||
|
||||
void SplictValueAndMergeChunksTest() {
|
||||
JemallocAllocatorOptions jopts;
|
||||
std::shared_ptr<MemoryAllocator> allocator;
|
||||
std::string msg;
|
||||
if (JemallocNodumpAllocator::IsSupported(&msg)) {
|
||||
Status s = NewJemallocNodumpAllocator(jopts, &allocator);
|
||||
if (!s.ok()) {
|
||||
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
||||
}
|
||||
} else {
|
||||
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
||||
}
|
||||
|
||||
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
||||
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
||||
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0,
|
||||
allocator);
|
||||
Random rnd(301);
|
||||
// 8500 = 8169 + 233 + 98, so there should be 3 chunks after split.
|
||||
size_t str_size{8500};
|
||||
std::string str = rnd.RandomString(static_cast<int>(str_size));
|
||||
size_t charge{0};
|
||||
CacheValueChunk* chunks_head =
|
||||
sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge);
|
||||
ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1));
|
||||
|
||||
CacheAllocationPtr value =
|
||||
sec_cache->MergeChunksIntoValue(chunks_head, charge);
|
||||
ASSERT_EQ(charge, str_size);
|
||||
std::string value_str{value.get(), charge};
|
||||
ASSERT_EQ(strcmp(value_str.data(), str.data()), 0);
|
||||
|
||||
sec_cache->GetDeletionCallback(true)("dummy", chunks_head);
|
||||
}
|
||||
|
||||
private:
|
||||
bool fail_create_;
|
||||
};
|
||||
|
||||
Cache::CacheItemHelper CompressedSecondaryCacheTest::helper_(
|
||||
CompressedSecondaryCacheTest::SizeCallback,
|
||||
CompressedSecondaryCacheTest::SaveToCallback,
|
||||
CompressedSecondaryCacheTest::DeletionCallback);
|
||||
|
||||
Cache::CacheItemHelper CompressedSecondaryCacheTest::helper_fail_(
|
||||
CompressedSecondaryCacheTest::SizeCallback,
|
||||
CompressedSecondaryCacheTest::SaveToCallbackFail,
|
||||
CompressedSecondaryCacheTest::DeletionCallback);
|
||||
|
||||
class CompressedSecCacheTestWithCompressAndAllocatorParam
|
||||
: public CompressedSecondaryCacheTest,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
CompressedSecCacheTestWithCompressAndAllocatorParam() {
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
use_jemalloc_ = std::get<1>(GetParam());
|
||||
}
|
||||
bool sec_cache_is_compressed_;
|
||||
bool use_jemalloc_;
|
||||
};
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithCompressAndAllocatorParam, BasicTes) {
|
||||
BasicTest(sec_cache_is_compressed_, use_jemalloc_);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
||||
CompressedSecCacheTestWithCompressAndAllocatorParam,
|
||||
::testing::Combine(testing::Bool(), testing::Bool()));
|
||||
|
||||
class CompressedSecondaryCacheTestWithCompressionParam
|
||||
: public CompressedSecondaryCacheTest,
|
||||
public ::testing::WithParamInterface<bool> {
|
||||
public:
|
||||
CompressedSecondaryCacheTestWithCompressionParam() {
|
||||
sec_cache_is_compressed_ = GetParam();
|
||||
}
|
||||
bool sec_cache_is_compressed_;
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam, BasicTestFromString) {
|
||||
std::shared_ptr<SecondaryCache> sec_cache{nullptr};
|
||||
std::string sec_cache_uri;
|
||||
if (sec_cache_is_compressed_) {
|
||||
if (LZ4_Supported()) {
|
||||
sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;"
|
||||
"compress_format_version=2";
|
||||
} else {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression";
|
||||
sec_cache_is_compressed_ = false;
|
||||
}
|
||||
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
||||
&sec_cache);
|
||||
EXPECT_OK(s);
|
||||
} else {
|
||||
sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression";
|
||||
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
||||
&sec_cache);
|
||||
EXPECT_OK(s);
|
||||
}
|
||||
BasicTestHelper(sec_cache, sec_cache_is_compressed_);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
||||
BasicTestFromStringWithSplit) {
|
||||
std::shared_ptr<SecondaryCache> sec_cache{nullptr};
|
||||
std::string sec_cache_uri;
|
||||
if (sec_cache_is_compressed_) {
|
||||
if (LZ4_Supported()) {
|
||||
sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;"
|
||||
"compress_format_version=2;enable_custom_split_merge=true";
|
||||
} else {
|
||||
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
||||
sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression;"
|
||||
"enable_custom_split_merge=true";
|
||||
sec_cache_is_compressed_ = false;
|
||||
}
|
||||
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
||||
&sec_cache);
|
||||
EXPECT_OK(s);
|
||||
} else {
|
||||
sec_cache_uri =
|
||||
"compressed_secondary_cache://"
|
||||
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression;"
|
||||
"enable_custom_split_merge=true";
|
||||
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
||||
&sec_cache);
|
||||
EXPECT_OK(s);
|
||||
}
|
||||
BasicTestHelper(sec_cache, sec_cache_is_compressed_);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam, FailsTest) {
|
||||
FailsTest(sec_cache_is_compressed_);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
||||
BasicIntegrationFailTest) {
|
||||
BasicIntegrationFailTest(sec_cache_is_compressed_);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
||||
IntegrationSaveFailTest) {
|
||||
IntegrationSaveFailTest(sec_cache_is_compressed_);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
||||
IntegrationCreateFailTest) {
|
||||
IntegrationCreateFailTest(sec_cache_is_compressed_);
|
||||
}
|
||||
|
||||
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
||||
IntegrationFullCapacityTest) {
|
||||
IntegrationFullCapacityTest(sec_cache_is_compressed_);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
||||
CompressedSecondaryCacheTestWithCompressionParam,
|
||||
testing::Bool());
|
||||
|
||||
class CompressedSecCacheTestWithCompressAndSplitParam
|
||||
: public CompressedSecondaryCacheTest,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
CompressedSecCacheTestWithCompressAndSplitParam() {
|
||||
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
||||
enable_custom_split_merge_ = std::get<1>(GetParam());
|
||||
}
|
||||
bool sec_cache_is_compressed_;
|
||||
bool enable_custom_split_merge_;
|
||||
};
|
||||
|
||||
TEST_P(CompressedSecCacheTestWithCompressAndSplitParam, BasicIntegrationTest) {
|
||||
BasicIntegrationTest(sec_cache_is_compressed_, enable_custom_split_merge_);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
||||
CompressedSecCacheTestWithCompressAndSplitParam,
|
||||
::testing::Combine(testing::Bool(), testing::Bool()));
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, SplitValueIntoChunksTest) {
|
||||
SplitValueIntoChunksTest();
|
||||
}
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, MergeChunksIntoValueTest) {
|
||||
MergeChunksIntoValueTest();
|
||||
}
|
||||
|
||||
TEST_F(CompressedSecondaryCacheTest, SplictValueAndMergeChunksTest) {
|
||||
SplictValueAndMergeChunksTest();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Vendored
-622
@@ -1,622 +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).
|
||||
//
|
||||
// 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)
|
||||
: CacheShard(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,
|
||||
uint32_t average_entries_per_lock, uint32_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_);
|
||||
uint32_t length_bits = table_.GetLengthBits();
|
||||
uint32_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);
|
||||
|
||||
uint32_t index_begin = *state >> (32 - length_bits);
|
||||
uint32_t index_end = index_begin + average_entries_per_lock;
|
||||
if (index_end >= length) {
|
||||
// Going to end
|
||||
index_end = length;
|
||||
*state = UINT32_MAX;
|
||||
} else {
|
||||
*state = index_end << (32 - 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,
|
||||
Cache::Handle** 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 = reinterpret_cast<Cache::Handle*>(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entries here outside of mutex for performance reasons.
|
||||
for (auto& h : last_reference_list) {
|
||||
h.FreeData();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Cache::Handle* 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 reinterpret_cast<Cache::Handle*>(h);
|
||||
}
|
||||
|
||||
bool LRUCacheShard::Ref(Cache::Handle* h) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(h);
|
||||
DMutexLock l(mutex_);
|
||||
// To create another reference - entry must be already externally referenced.
|
||||
assert(e->HasRefs());
|
||||
e->Ref();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LRUCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
if (handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
LRUHandle* h = reinterpret_cast<LRUHandle*>(handle);
|
||||
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();
|
||||
}
|
||||
|
||||
std::string LRUCacheShard::GetPrintableOptions() const { return std::string{}; }
|
||||
|
||||
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) {
|
||||
assert(estimated_value_size > 0 ||
|
||||
metadata_charge_policy != kDontChargeCacheMetadata);
|
||||
num_shards_ = 1 << num_shard_bits;
|
||||
shards_ = reinterpret_cast<LRUCacheShard*>(
|
||||
port::cacheline_aligned_alloc(sizeof(LRUCacheShard) * num_shards_));
|
||||
size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
new (&shards_[i])
|
||||
LRUCacheShard(per_shard, estimated_value_size, strict_capacity_limit,
|
||||
metadata_charge_policy);
|
||||
}
|
||||
}
|
||||
|
||||
LRUCache::~LRUCache() {
|
||||
if (shards_ != nullptr) {
|
||||
assert(num_shards_ > 0);
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
shards_[i].~LRUCacheShard();
|
||||
}
|
||||
port::cacheline_aligned_free(shards_);
|
||||
}
|
||||
}
|
||||
|
||||
CacheShard* LRUCache::GetShard(uint32_t shard) {
|
||||
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
||||
}
|
||||
|
||||
const CacheShard* LRUCache::GetShard(uint32_t shard) const {
|
||||
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
||||
}
|
||||
|
||||
void* LRUCache::Value(Handle* handle) {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->value;
|
||||
}
|
||||
|
||||
size_t LRUCache::GetCharge(Handle* handle) const {
|
||||
CacheMetadataChargePolicy metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
if (num_shards_ > 0) {
|
||||
metadata_charge_policy = shards_[0].metadata_charge_policy_;
|
||||
}
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->GetCharge(
|
||||
metadata_charge_policy);
|
||||
}
|
||||
|
||||
Cache::DeleterFn LRUCache::GetDeleter(Handle* handle) const {
|
||||
auto h = reinterpret_cast<const LRUHandle*>(handle);
|
||||
return h->deleter;
|
||||
}
|
||||
|
||||
uint32_t LRUCache::GetHash(Handle* handle) const {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->hash;
|
||||
}
|
||||
|
||||
void LRUCache::DisownData() {
|
||||
// Leak data only if that won't generate an ASAN/valgrind warning.
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // 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
-476
@@ -1,476 +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).
|
||||
//
|
||||
// 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); }
|
||||
|
||||
// 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, uint32_t index_begin, uint32_t index_end) {
|
||||
for (uint32_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 CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, size_t estimated_value_size,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
~LRUCacheShard() override = default;
|
||||
|
||||
// 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) override;
|
||||
|
||||
// Set the flag to reject insertion if cache if full.
|
||||
void SetStrictCapacityLimit(bool strict_capacity_limit) override;
|
||||
|
||||
// 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, Cache::Handle** handle,
|
||||
Cache::Priority priority) override;
|
||||
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
Cache::Handle** handle, Cache::Priority priority) override {
|
||||
return Insert(key, hash, value, charge, helper->del_cb, handle, priority);
|
||||
}
|
||||
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
const Cache::CreateCallback& /*create_cb*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/,
|
||||
Statistics* /*stats*/) override {
|
||||
return Lookup(key, hash);
|
||||
}
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
|
||||
bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool erase_if_last_ref) override {
|
||||
return Release(handle, erase_if_last_ref);
|
||||
}
|
||||
bool IsReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
void Wait(Cache::Handle* /*handle*/) override {}
|
||||
|
||||
bool Ref(Cache::Handle* handle) override;
|
||||
bool Release(Cache::Handle* handle, bool erase_if_last_ref = false) override;
|
||||
void Erase(const Slice& key, uint32_t hash) override;
|
||||
|
||||
size_t GetUsage() const override;
|
||||
size_t GetPinnedUsage() const override;
|
||||
size_t GetOccupancyCount() const override;
|
||||
size_t GetTableAddressCount() const override;
|
||||
|
||||
void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
uint32_t average_entries_per_lock, uint32_t* state) override;
|
||||
|
||||
void EraseUnRefEntries() override;
|
||||
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
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 {
|
||||
public:
|
||||
LRUCache(size_t capacity, size_t estimated_value_size, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata);
|
||||
~LRUCache() override;
|
||||
const char* Name() const override { return "LRUCache"; }
|
||||
CacheShard* GetShard(uint32_t shard) override;
|
||||
const CacheShard* GetShard(uint32_t shard) const override;
|
||||
void* Value(Handle* handle) override;
|
||||
size_t GetCharge(Handle* handle) const override;
|
||||
uint32_t GetHash(Handle* handle) const override;
|
||||
DeleterFn GetDeleter(Handle* handle) const override;
|
||||
void DisownData() override;
|
||||
|
||||
private:
|
||||
LRUCacheShard* shards_ = nullptr;
|
||||
int num_shards_ = 0;
|
||||
};
|
||||
} // 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
+158
-326
@@ -12,15 +12,12 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "port/lang.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace lru_cache {
|
||||
|
||||
LRUHandleTable::LRUHandleTable(int max_upper_hash_bits)
|
||||
: length_bits_(/* historical starting size*/ 4),
|
||||
@@ -78,12 +75,13 @@ LRUHandle** LRUHandleTable::FindPointer(const Slice& key, uint32_t hash) {
|
||||
|
||||
void LRUHandleTable::Resize() {
|
||||
if (length_bits_ >= max_length_bits_) {
|
||||
// Due to reaching limit of hash information, if we made the table bigger,
|
||||
// we would allocate more addresses but only the same number would be used.
|
||||
// Due to reaching limit of hash information, if we made the table
|
||||
// bigger, we would allocate more addresses but only the same
|
||||
// number would be used.
|
||||
return;
|
||||
}
|
||||
if (length_bits_ >= 31) {
|
||||
// Avoid undefined behavior shifting uint32_t by 32.
|
||||
// Avoid undefined behavior shifting uint32_t by 32
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -112,44 +110,41 @@ void LRUHandleTable::Resize() {
|
||||
|
||||
LRUCacheShard::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,
|
||||
bool use_adaptive_mutex, CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache)
|
||||
: CacheShard(metadata_charge_policy),
|
||||
capacity_(0),
|
||||
: capacity_(0),
|
||||
high_pri_pool_usage_(0),
|
||||
low_pri_pool_usage_(0),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
high_pri_pool_ratio_(high_pri_pool_ratio),
|
||||
high_pri_pool_capacity_(0),
|
||||
low_pri_pool_ratio_(low_pri_pool_ratio),
|
||||
low_pri_pool_capacity_(0),
|
||||
table_(max_upper_hash_bits),
|
||||
usage_(0),
|
||||
lru_usage_(0),
|
||||
mutex_(use_adaptive_mutex),
|
||||
secondary_cache_(secondary_cache) {
|
||||
// Make empty circular linked list.
|
||||
set_metadata_charge_policy(metadata_charge_policy);
|
||||
// Make empty circular linked list
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
lru_low_pri_ = &lru_;
|
||||
lru_bottom_pri_ = &lru_;
|
||||
SetCapacity(capacity);
|
||||
}
|
||||
|
||||
void LRUCacheShard::EraseUnRefEntries() {
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
while (lru_.next != &lru_) {
|
||||
LRUHandle* old = lru_.next;
|
||||
// LRU list contains only elements which can be evicted.
|
||||
// LRU list contains only elements which can be evicted
|
||||
assert(old->InCache() && !old->HasRefs());
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old->key(), old->hash);
|
||||
old->SetInCache(false);
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
size_t total_charge = old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
@@ -166,13 +161,13 @@ void LRUCacheShard::ApplyToSomeEntries(
|
||||
// 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_);
|
||||
MutexLock l(&mutex_);
|
||||
uint32_t length_bits = table_.GetLengthBits();
|
||||
uint32_t length = uint32_t{1} << length_bits;
|
||||
|
||||
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).
|
||||
// this simplifies some logic (index_end will not overflow)
|
||||
assert(average_entries_per_lock < length || *state == 0);
|
||||
|
||||
uint32_t index_begin = *state >> (32 - length_bits);
|
||||
@@ -186,27 +181,23 @@ void LRUCacheShard::ApplyToSomeEntries(
|
||||
}
|
||||
|
||||
table_.ApplyToEntriesRange(
|
||||
[callback,
|
||||
metadata_charge_policy = metadata_charge_policy_](LRUHandle* h) {
|
||||
[callback](LRUHandle* h) {
|
||||
DeleterFn deleter = h->IsSecondaryCacheCompatible()
|
||||
? h->info_.helper->del_cb
|
||||
: h->info_.deleter;
|
||||
callback(h->key(), h->value, h->GetCharge(metadata_charge_policy),
|
||||
deleter);
|
||||
callback(h->key(), h->value, h->charge, deleter);
|
||||
},
|
||||
index_begin, index_end);
|
||||
}
|
||||
|
||||
void LRUCacheShard::TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri,
|
||||
LRUHandle** lru_bottom_pri) {
|
||||
DMutexLock l(mutex_);
|
||||
void LRUCacheShard::TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri) {
|
||||
MutexLock l(&mutex_);
|
||||
*lru = &lru_;
|
||||
*lru_low_pri = lru_low_pri_;
|
||||
*lru_bottom_pri = lru_bottom_pri_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::TEST_GetLRUSize() {
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
LRUHandle* lru_handle = lru_.next;
|
||||
size_t lru_size = 0;
|
||||
while (lru_handle != &lru_) {
|
||||
@@ -217,42 +208,32 @@ size_t LRUCacheShard::TEST_GetLRUSize() {
|
||||
}
|
||||
|
||||
double LRUCacheShard::GetHighPriPoolRatio() {
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
return high_pri_pool_ratio_;
|
||||
}
|
||||
|
||||
double LRUCacheShard::GetLowPriPoolRatio() {
|
||||
DMutexLock l(mutex_);
|
||||
return low_pri_pool_ratio_;
|
||||
}
|
||||
|
||||
void LRUCacheShard::LRU_Remove(LRUHandle* e) {
|
||||
assert(e->next != nullptr);
|
||||
assert(e->prev != nullptr);
|
||||
if (lru_low_pri_ == e) {
|
||||
lru_low_pri_ = e->prev;
|
||||
}
|
||||
if (lru_bottom_pri_ == e) {
|
||||
lru_bottom_pri_ = e->prev;
|
||||
}
|
||||
e->next->prev = e->prev;
|
||||
e->prev->next = e->next;
|
||||
e->prev = e->next = nullptr;
|
||||
assert(lru_usage_ >= e->total_charge);
|
||||
lru_usage_ -= e->total_charge;
|
||||
assert(!e->InHighPriPool() || !e->InLowPriPool());
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(lru_usage_ >= total_charge);
|
||||
lru_usage_ -= total_charge;
|
||||
if (e->InHighPriPool()) {
|
||||
assert(high_pri_pool_usage_ >= e->total_charge);
|
||||
high_pri_pool_usage_ -= e->total_charge;
|
||||
} else if (e->InLowPriPool()) {
|
||||
assert(low_pri_pool_usage_ >= e->total_charge);
|
||||
low_pri_pool_usage_ -= e->total_charge;
|
||||
assert(high_pri_pool_usage_ >= total_charge);
|
||||
high_pri_pool_usage_ -= total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::LRU_Insert(LRUHandle* e) {
|
||||
assert(e->next == nullptr);
|
||||
assert(e->prev == nullptr);
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
if (high_pri_pool_ratio_ > 0 && (e->IsHighPri() || e->HasHit())) {
|
||||
// Inset "e" to head of LRU list.
|
||||
e->next = &lru_;
|
||||
@@ -260,36 +241,19 @@ void LRUCacheShard::LRU_Insert(LRUHandle* e) {
|
||||
e->prev->next = e;
|
||||
e->next->prev = e;
|
||||
e->SetInHighPriPool(true);
|
||||
e->SetInLowPriPool(false);
|
||||
high_pri_pool_usage_ += e->total_charge;
|
||||
high_pri_pool_usage_ += total_charge;
|
||||
MaintainPoolSize();
|
||||
} else if (low_pri_pool_ratio_ > 0 &&
|
||||
(e->IsHighPri() || e->IsLowPri() || e->HasHit())) {
|
||||
// Insert "e" to the head of low-pri pool.
|
||||
} else {
|
||||
// Insert "e" to the head of low-pri pool. Note that when
|
||||
// high_pri_pool_ratio is 0, head of low-pri pool is also head of LRU list.
|
||||
e->next = lru_low_pri_->next;
|
||||
e->prev = lru_low_pri_;
|
||||
e->prev->next = e;
|
||||
e->next->prev = e;
|
||||
e->SetInHighPriPool(false);
|
||||
e->SetInLowPriPool(true);
|
||||
low_pri_pool_usage_ += e->total_charge;
|
||||
MaintainPoolSize();
|
||||
lru_low_pri_ = e;
|
||||
} else {
|
||||
// Insert "e" to the head of bottom-pri pool.
|
||||
e->next = lru_bottom_pri_->next;
|
||||
e->prev = lru_bottom_pri_;
|
||||
e->prev->next = e;
|
||||
e->next->prev = e;
|
||||
e->SetInHighPriPool(false);
|
||||
e->SetInLowPriPool(false);
|
||||
// if the low-pri pool is empty, lru_low_pri_ also needs to be updated.
|
||||
if (lru_bottom_pri_ == lru_low_pri_) {
|
||||
lru_low_pri_ = e;
|
||||
}
|
||||
lru_bottom_pri_ = e;
|
||||
}
|
||||
lru_usage_ += e->total_charge;
|
||||
lru_usage_ += total_charge;
|
||||
}
|
||||
|
||||
void LRUCacheShard::MaintainPoolSize() {
|
||||
@@ -298,20 +262,10 @@ void LRUCacheShard::MaintainPoolSize() {
|
||||
lru_low_pri_ = lru_low_pri_->next;
|
||||
assert(lru_low_pri_ != &lru_);
|
||||
lru_low_pri_->SetInHighPriPool(false);
|
||||
lru_low_pri_->SetInLowPriPool(true);
|
||||
assert(high_pri_pool_usage_ >= lru_low_pri_->total_charge);
|
||||
high_pri_pool_usage_ -= lru_low_pri_->total_charge;
|
||||
low_pri_pool_usage_ += lru_low_pri_->total_charge;
|
||||
}
|
||||
|
||||
while (low_pri_pool_usage_ > low_pri_pool_capacity_) {
|
||||
// Overflow last entry in low-pri pool to bottom-pri pool.
|
||||
lru_bottom_pri_ = lru_bottom_pri_->next;
|
||||
assert(lru_bottom_pri_ != &lru_);
|
||||
lru_bottom_pri_->SetInHighPriPool(false);
|
||||
lru_bottom_pri_->SetInLowPriPool(false);
|
||||
assert(low_pri_pool_usage_ >= lru_bottom_pri_->total_charge);
|
||||
low_pri_pool_usage_ -= lru_bottom_pri_->total_charge;
|
||||
size_t total_charge =
|
||||
lru_low_pri_->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(high_pri_pool_usage_ >= total_charge);
|
||||
high_pri_pool_usage_ -= total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,45 +273,41 @@ 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.
|
||||
// LRU list contains only elements which can be evicted
|
||||
assert(old->InCache() && !old->HasRefs());
|
||||
LRU_Remove(old);
|
||||
table_.Remove(old->key(), old->hash);
|
||||
old->SetInCache(false);
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
size_t old_total_charge = old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
deleted->push_back(old);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
capacity_ = capacity;
|
||||
high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
|
||||
low_pri_pool_capacity_ = capacity_ * low_pri_pool_ratio_;
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
}
|
||||
|
||||
TryInsertIntoSecondaryCache(last_reference_list);
|
||||
// Try to insert the evicted entries into tiered cache
|
||||
// Free the entries outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
if (secondary_cache_ && entry->IsSecondaryCacheCompatible() &&
|
||||
!entry->IsPromoted()) {
|
||||
secondary_cache_->Insert(entry->key(), entry->value, entry->info_.helper)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
entry->Free();
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
@@ -365,15 +315,16 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
bool free_handle_on_fail) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock 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);
|
||||
// is freed or the lru list is empty
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + e->total_charge) > capacity_ &&
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
e->SetInCache(false);
|
||||
if (handle == nullptr) {
|
||||
@@ -382,32 +333,34 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
if (free_handle_on_fail) {
|
||||
free(e);
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
*handle = nullptr;
|
||||
}
|
||||
s = Status::MemoryLimit("Insert failed due to LRU cache being full.");
|
||||
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
usage_ += e->total_charge;
|
||||
usage_ += total_charge;
|
||||
if (old != nullptr) {
|
||||
s = Status::OkOverwritten();
|
||||
assert(old->InCache());
|
||||
old->SetInCache(false);
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0.
|
||||
// old is on LRU because it's in cache and its reference count is 0
|
||||
LRU_Remove(old);
|
||||
assert(usage_ >= old->total_charge);
|
||||
usage_ -= old->total_charge;
|
||||
size_t old_total_charge =
|
||||
old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(e);
|
||||
} else {
|
||||
// If caller already holds a ref, no need to take one here.
|
||||
// If caller already holds a ref, no need to take one here
|
||||
if (!e->HasRefs()) {
|
||||
e->Ref();
|
||||
}
|
||||
@@ -416,7 +369,16 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
}
|
||||
}
|
||||
|
||||
TryInsertIntoSecondaryCache(last_reference_list);
|
||||
// Try to insert the evicted entries into the secondary cache
|
||||
// Free the entries here outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
if (secondary_cache_ && entry->IsSecondaryCacheCompatible() &&
|
||||
!entry->IsPromoted()) {
|
||||
secondary_cache_->Insert(entry->key(), entry->value, entry->info_.helper)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
entry->Free();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
@@ -426,78 +388,30 @@ void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
|
||||
assert(secondary_handle->IsReady());
|
||||
e->SetIncomplete(false);
|
||||
e->SetInCache(false);
|
||||
e->SetInCache(true);
|
||||
e->SetPromoted(true);
|
||||
e->value = secondary_handle->Value();
|
||||
e->CalcTotalCharge(secondary_handle->Size(), metadata_charge_policy_);
|
||||
e->charge = secondary_handle->Size();
|
||||
delete secondary_handle;
|
||||
|
||||
// This call 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 from disk if we erase it here.
|
||||
if (e->value) {
|
||||
Status s;
|
||||
if (secondary_cache_ && secondary_cache_->SupportForceErase() &&
|
||||
e->IsStandalone()) {
|
||||
// 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().
|
||||
Cache::Priority priority =
|
||||
e->IsHighPri() ? Cache::Priority::HIGH : Cache::Priority::LOW;
|
||||
s = Insert(e->key(), e->hash, /*value=*/nullptr, 0,
|
||||
/*deleter=*/nullptr, /*helper=*/nullptr, /*handle=*/nullptr,
|
||||
priority);
|
||||
} else {
|
||||
e->SetInCache(true);
|
||||
e->SetIsStandalone(false);
|
||||
Cache::Handle* handle = reinterpret_cast<Cache::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);
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Handle* handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
Status s = InsertItem(e, &handle, /*free_handle_on_fail=*/false);
|
||||
if (!s.ok()) {
|
||||
// Item is in memory, but not accounted against the cache capacity.
|
||||
// When the handle is released, the item should get deleted.
|
||||
// When the handle is released, the item should get deleted
|
||||
assert(!e->InCache());
|
||||
}
|
||||
|
||||
} else {
|
||||
// Since the secondary cache lookup failed, mark the item as not in cache
|
||||
// Don't charge the cache as its only metadata that'll shortly be released
|
||||
DMutexLock l(mutex_);
|
||||
// TODO
|
||||
e->CalcTotalCharge(0, metadata_charge_policy_);
|
||||
MutexLock l(&mutex_);
|
||||
e->charge = 0;
|
||||
e->SetInCache(false);
|
||||
e->SetIsStandalone(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,61 +421,36 @@ Cache::Handle* LRUCacheShard::Lookup(
|
||||
const ShardedCache::CreateCallback& create_cb, Cache::Priority priority,
|
||||
bool wait, Statistics* stats) {
|
||||
LRUHandle* e = nullptr;
|
||||
bool found_dummy_entry{false};
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
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.
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
|
||||
// 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.
|
||||
if (!e->value && secondary_cache_ &&
|
||||
secondary_cache_->SupportForceErase()) {
|
||||
found_dummy_entry = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// Only support synchronous for now.
|
||||
// If handle table lookup failed, then allocate a handle outside the
|
||||
// mutex if we're going to lookup in the secondary cache
|
||||
// Only support synchronous for now
|
||||
// TODO: Support asynchronous lookup in secondary cache
|
||||
if ((!e || found_dummy_entry) && secondary_cache_ && helper &&
|
||||
helper->saveto_cb) {
|
||||
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);
|
||||
// Release the dummy handle.
|
||||
if (e) {
|
||||
Release(reinterpret_cast<Cache::Handle*>(e), true /*erase_if_last_ref*/);
|
||||
}
|
||||
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);
|
||||
secondary_cache_->Lookup(key, create_cb, wait);
|
||||
if (secondary_handle != nullptr) {
|
||||
e = static_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
|
||||
e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
|
||||
e->flags = 0;
|
||||
e->SetSecondaryCacheCompatible(true);
|
||||
@@ -574,38 +463,28 @@ Cache::Handle* LRUCacheShard::Lookup(
|
||||
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);
|
||||
|
||||
if (secondary_cache_->SupportForceErase() && !found_dummy_entry) {
|
||||
e->SetIsStandalone(true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
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.
|
||||
// release the handle after checking for success or failure
|
||||
e->SetIncomplete(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 {
|
||||
e = nullptr;
|
||||
}
|
||||
}
|
||||
return reinterpret_cast<Cache::Handle*>(e);
|
||||
@@ -613,46 +492,39 @@ Cache::Handle* LRUCacheShard::Lookup(
|
||||
|
||||
bool LRUCacheShard::Ref(Cache::Handle* h) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(h);
|
||||
DMutexLock l(mutex_);
|
||||
// To create another reference - entry must be already externally referenced.
|
||||
MutexLock l(&mutex_);
|
||||
// To create another reference - entry must be already externally referenced
|
||||
assert(e->HasRefs());
|
||||
e->Ref();
|
||||
return true;
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetHighPriorityPoolRatio(double high_pri_pool_ratio) {
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
high_pri_pool_ratio_ = high_pri_pool_ratio;
|
||||
high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
|
||||
MaintainPoolSize();
|
||||
}
|
||||
|
||||
void LRUCacheShard::SetLowPriorityPoolRatio(double low_pri_pool_ratio) {
|
||||
DMutexLock l(mutex_);
|
||||
low_pri_pool_ratio_ = low_pri_pool_ratio;
|
||||
low_pri_pool_capacity_ = capacity_ * low_pri_pool_ratio_;
|
||||
MaintainPoolSize();
|
||||
}
|
||||
|
||||
bool LRUCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
|
||||
if (handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
||||
bool last_reference = false;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
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.
|
||||
assert(lru_.next == &lru_ || erase_if_last_ref);
|
||||
// Take this opportunity and remove the item.
|
||||
// The item is still in cache, and nobody else holds a reference to it
|
||||
if (usage_ > capacity_ || force_erase) {
|
||||
// The LRU list must be empty since the cache is full
|
||||
assert(lru_.next == &lru_ || force_erase);
|
||||
// Take this opportunity and remove the item
|
||||
table_.Remove(e->key(), e->hash);
|
||||
e->SetInCache(false);
|
||||
} else {
|
||||
// Put the item back on the LRU list, and don't free it.
|
||||
// Put the item back on the LRU list, and don't free it
|
||||
LRU_Insert(e);
|
||||
last_reference = false;
|
||||
}
|
||||
@@ -660,15 +532,16 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool erase_if_last_ref) {
|
||||
// If it was the last reference, and the entry is either not secondary
|
||||
// cache compatible (i.e a dummy entry for accounting), or is secondary
|
||||
// cache compatible and has a non-null value, then decrement the cache
|
||||
// usage. If value is null in the latter case, that means the lookup
|
||||
// usage. If value is null in the latter case, taht means the lookup
|
||||
// failed and we didn't charge the cache.
|
||||
if (last_reference && (!e->IsSecondaryCacheCompatible() || e->value)) {
|
||||
assert(usage_ >= e->total_charge);
|
||||
usage_ -= e->total_charge;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entry here outside of mutex for performance reasons.
|
||||
// Free the entry here outside of mutex for performance reasons
|
||||
if (last_reference) {
|
||||
e->Free();
|
||||
}
|
||||
@@ -680,11 +553,11 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::Handle** handle, Cache::Priority priority) {
|
||||
// Allocate the memory here outside of the mutex.
|
||||
// If the cache is full, we'll have to release it.
|
||||
// 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.
|
||||
LRUHandle* e =
|
||||
static_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
|
||||
e->value = value;
|
||||
e->flags = 0;
|
||||
@@ -697,6 +570,7 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
#endif // __SANITIZE_THREAD__
|
||||
e->info_.deleter = deleter;
|
||||
}
|
||||
e->charge = charge;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
@@ -704,7 +578,6 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->CalcTotalCharge(charge, metadata_charge_policy_);
|
||||
|
||||
return InsertItem(e, handle, /* free_handle_on_fail */ true);
|
||||
}
|
||||
@@ -713,7 +586,7 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
LRUHandle* e;
|
||||
bool last_reference = false;
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
e = table_.Remove(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
@@ -721,15 +594,16 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
assert(usage_ >= e->total_charge);
|
||||
usage_ -= e->total_charge;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
last_reference = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entry here outside of mutex for performance reasons.
|
||||
// last_reference will only be true if e != nullptr.
|
||||
// Free the entry here outside of mutex for performance reasons
|
||||
// last_reference will only be true if e != nullptr
|
||||
if (last_reference) {
|
||||
e->Free();
|
||||
}
|
||||
@@ -737,7 +611,7 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
|
||||
bool LRUCacheShard::IsReady(Cache::Handle* handle) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
bool ready = true;
|
||||
if (e->IsPending()) {
|
||||
assert(secondary_cache_);
|
||||
@@ -748,42 +622,29 @@ bool LRUCacheShard::IsReady(Cache::Handle* handle) {
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetUsage() const {
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
return usage_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetPinnedUsage() const {
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
assert(usage_ >= lru_usage_);
|
||||
return usage_ - lru_usage_;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetOccupancyCount() const {
|
||||
DMutexLock l(mutex_);
|
||||
return table_.GetOccupancyCount();
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetTableAddressCount() const {
|
||||
DMutexLock l(mutex_);
|
||||
return size_t{1} << table_.GetLengthBits();
|
||||
}
|
||||
|
||||
std::string LRUCacheShard::GetPrintableOptions() const {
|
||||
const int kBufferSize = 200;
|
||||
char buffer[kBufferSize];
|
||||
{
|
||||
DMutexLock l(mutex_);
|
||||
MutexLock l(&mutex_);
|
||||
snprintf(buffer, kBufferSize, " high_pri_pool_ratio: %.3lf\n",
|
||||
high_pri_pool_ratio_);
|
||||
snprintf(buffer + strlen(buffer), kBufferSize - strlen(buffer),
|
||||
" low_pri_pool_ratio: %.3lf\n", low_pri_pool_ratio_);
|
||||
}
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -797,7 +658,7 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
new (&shards_[i]) LRUCacheShard(
|
||||
per_shard, strict_capacity_limit, high_pri_pool_ratio,
|
||||
low_pri_pool_ratio, use_adaptive_mutex, metadata_charge_policy,
|
||||
use_adaptive_mutex, metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - num_shard_bits, secondary_cache);
|
||||
}
|
||||
secondary_cache_ = secondary_cache;
|
||||
@@ -826,12 +687,7 @@ void* LRUCache::Value(Handle* handle) {
|
||||
}
|
||||
|
||||
size_t LRUCache::GetCharge(Handle* handle) const {
|
||||
CacheMetadataChargePolicy metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
if (num_shards_ > 0) {
|
||||
metadata_charge_policy = shards_[0].metadata_charge_policy_;
|
||||
}
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->GetCharge(
|
||||
metadata_charge_policy);
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->charge;
|
||||
}
|
||||
|
||||
Cache::DeleterFn LRUCache::GetDeleter(Handle* handle) const {
|
||||
@@ -848,11 +704,11 @@ uint32_t LRUCache::GetHash(Handle* handle) const {
|
||||
}
|
||||
|
||||
void LRUCache::DisownData() {
|
||||
// Leak data only if that won't generate an ASAN/valgrind warning.
|
||||
if (!kMustFreeHeapAllocations) {
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
}
|
||||
// Do not drop data if compile with ASAN to suppress leak warning.
|
||||
#ifndef MUST_FREE_HEAP_ALLOCATIONS
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t LRUCache::TEST_GetLRUSize() {
|
||||
@@ -901,39 +757,17 @@ void LRUCache::WaitAll(std::vector<Handle*>& handles) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string LRUCache::GetPrintableOptions() const {
|
||||
std::string ret;
|
||||
ret.reserve(20000);
|
||||
ret.append(ShardedCache::GetPrintableOptions());
|
||||
if (secondary_cache_) {
|
||||
ret.append(" secondary_cache:\n");
|
||||
ret.append(secondary_cache_->GetPrintableOptions());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace lru_cache
|
||||
|
||||
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) {
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // The cache cannot be sharded into too many fine pieces.
|
||||
return nullptr; // the cache cannot be sharded into too many fine pieces
|
||||
}
|
||||
if (high_pri_pool_ratio < 0.0 || high_pri_pool_ratio > 1.0) {
|
||||
// Invalid high_pri_pool_ratio
|
||||
return nullptr;
|
||||
}
|
||||
if (low_pri_pool_ratio < 0.0 || low_pri_pool_ratio > 1.0) {
|
||||
// Invalid high_pri_pool_ratio
|
||||
return nullptr;
|
||||
}
|
||||
if (low_pri_pool_ratio + high_pri_pool_ratio > 1.0) {
|
||||
// Invalid high_pri_pool_ratio and low_pri_pool_ratio combination
|
||||
// invalid high_pri_pool_ratio
|
||||
return nullptr;
|
||||
}
|
||||
if (num_shard_bits < 0) {
|
||||
@@ -941,27 +775,25 @@ std::shared_ptr<Cache> NewLRUCache(
|
||||
}
|
||||
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::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy,
|
||||
secondary_cache);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
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);
|
||||
metadata_charge_policy, nullptr);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+47
-133
@@ -17,10 +17,8 @@
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/distributed_mutex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace lru_cache {
|
||||
|
||||
// LRU cache implementation. This class is not thread-safe.
|
||||
|
||||
@@ -40,11 +38,8 @@ namespace lru_cache {
|
||||
// In that case the entry is not in the LRU list and not in hash table.
|
||||
// The entry can be freed when refs becomes 0.
|
||||
// (refs >= 1 && in_cache == false)
|
||||
// 4. The handle is never inserted into the LRUCache (both hash table and LRU
|
||||
// list) and it doesn't experience the above three states.
|
||||
// The entry can be freed when refs becomes 0.
|
||||
// (refs >= 1 && in_cache == false && IS_STANDALONE == true)
|
||||
// All newly created LRUHandles are in state 1 or 4. If you call
|
||||
//
|
||||
// All newly created LRUHandles are in state 1. If you call
|
||||
// LRUCacheShard::Release on entry in state 1, it will go into state 2.
|
||||
// To move from state 1 to state 3, either call LRUCacheShard::Erase or
|
||||
// LRUCacheShard::Insert with the same key (but possibly different value).
|
||||
@@ -70,14 +65,14 @@ struct LRUHandle {
|
||||
};
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t total_charge; // TODO(opt): Only allow uint32_t?
|
||||
size_t charge; // TODO(opt): Only allow uint32_t?
|
||||
size_t key_length;
|
||||
// The hash of key(). Used for fast sharding and comparisons.
|
||||
uint32_t hash;
|
||||
// The number of external refs to this entry. The cache itself is not counted.
|
||||
uint32_t refs;
|
||||
|
||||
enum Flags : uint16_t {
|
||||
enum Flags : uint8_t {
|
||||
// Whether this entry is referenced by the hash table.
|
||||
IN_CACHE = (1 << 0),
|
||||
// Whether this entry is high priority entry.
|
||||
@@ -86,22 +81,15 @@ struct LRUHandle {
|
||||
IN_HIGH_PRI_POOL = (1 << 2),
|
||||
// Whether this entry has had any lookups (hits).
|
||||
HAS_HIT = (1 << 3),
|
||||
// Can this be inserted into the secondary cache.
|
||||
// Can this be inserted into the secondary cache
|
||||
IS_SECONDARY_CACHE_COMPATIBLE = (1 << 4),
|
||||
// Is the handle still being read from a lower tier.
|
||||
// Is the handle still being read from a lower tier
|
||||
IS_PENDING = (1 << 5),
|
||||
// Whether this handle is still in a lower tier
|
||||
IS_IN_SECONDARY_CACHE = (1 << 6),
|
||||
// Whether this entry is low priority entry.
|
||||
IS_LOW_PRI = (1 << 7),
|
||||
// Whether this entry is in low-pri pool.
|
||||
IN_LOW_PRI_POOL = (1 << 8),
|
||||
// Whether this entry is not inserted into the cache (both hash table and
|
||||
// LRU list).
|
||||
IS_STANDALONE = (1 << 9),
|
||||
// Has the item been promoted from a lower tier
|
||||
IS_PROMOTED = (1 << 6),
|
||||
};
|
||||
|
||||
uint16_t flags;
|
||||
uint8_t flags;
|
||||
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
// TSAN can report a false data race on flags, where one thread is writing
|
||||
@@ -132,8 +120,6 @@ struct LRUHandle {
|
||||
bool InCache() const { return flags & IN_CACHE; }
|
||||
bool IsHighPri() const { return flags & IS_HIGH_PRI; }
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
bool IsLowPri() const { return flags & IS_LOW_PRI; }
|
||||
bool InLowPriPool() const { return flags & IN_LOW_PRI_POOL; }
|
||||
bool HasHit() const { return flags & HAS_HIT; }
|
||||
bool IsSecondaryCacheCompatible() const {
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
@@ -143,8 +129,7 @@ struct LRUHandle {
|
||||
#endif // __SANITIZE_THREAD__
|
||||
}
|
||||
bool IsPending() const { return flags & IS_PENDING; }
|
||||
bool IsInSecondaryCache() const { return flags & IS_IN_SECONDARY_CACHE; }
|
||||
bool IsStandalone() const { return flags & IS_STANDALONE; }
|
||||
bool IsPromoted() const { return flags & IS_PROMOTED; }
|
||||
|
||||
void SetInCache(bool in_cache) {
|
||||
if (in_cache) {
|
||||
@@ -157,13 +142,8 @@ struct LRUHandle {
|
||||
void SetPriority(Cache::Priority priority) {
|
||||
if (priority == Cache::Priority::HIGH) {
|
||||
flags |= IS_HIGH_PRI;
|
||||
flags &= ~IS_LOW_PRI;
|
||||
} else if (priority == Cache::Priority::LOW) {
|
||||
flags &= ~IS_HIGH_PRI;
|
||||
flags |= IS_LOW_PRI;
|
||||
} else {
|
||||
flags &= ~IS_HIGH_PRI;
|
||||
flags &= ~IS_LOW_PRI;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,14 +155,6 @@ struct LRUHandle {
|
||||
}
|
||||
}
|
||||
|
||||
void SetInLowPriPool(bool in_low_pri_pool) {
|
||||
if (in_low_pri_pool) {
|
||||
flags |= IN_LOW_PRI_POOL;
|
||||
} else {
|
||||
flags &= ~IN_LOW_PRI_POOL;
|
||||
}
|
||||
}
|
||||
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void SetSecondaryCacheCompatible(bool compat) {
|
||||
@@ -204,19 +176,11 @@ struct LRUHandle {
|
||||
}
|
||||
}
|
||||
|
||||
void SetIsInSecondaryCache(bool is_in_secondary_cache) {
|
||||
if (is_in_secondary_cache) {
|
||||
flags |= IS_IN_SECONDARY_CACHE;
|
||||
void SetPromoted(bool promoted) {
|
||||
if (promoted) {
|
||||
flags |= IS_PROMOTED;
|
||||
} else {
|
||||
flags &= ~IS_IN_SECONDARY_CACHE;
|
||||
}
|
||||
}
|
||||
|
||||
void SetIsStandalone(bool is_standalone) {
|
||||
if (is_standalone) {
|
||||
flags |= IS_STANDALONE;
|
||||
} else {
|
||||
flags &= ~IS_STANDALONE;
|
||||
flags &= ~IS_PROMOTED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,36 +205,22 @@ struct LRUHandle {
|
||||
(*info_.helper->del_cb)(key(), value);
|
||||
}
|
||||
}
|
||||
|
||||
free(this);
|
||||
delete[] reinterpret_cast<char*>(this);
|
||||
}
|
||||
|
||||
inline size_t CalcuMetaCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) const {
|
||||
if (metadata_charge_policy != kFullChargeCacheMetadata) {
|
||||
return 0;
|
||||
} else {
|
||||
// Calculate the memory usage by metadata
|
||||
inline size_t CalcTotalCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
size_t meta_charge = 0;
|
||||
if (metadata_charge_policy == kFullChargeCacheMetadata) {
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
return malloc_usable_size(
|
||||
const_cast<void*>(static_cast<const void*>(this)));
|
||||
meta_charge += malloc_usable_size(static_cast<void*>(this));
|
||||
#else
|
||||
// This is the size that is used when a new handle is created.
|
||||
return sizeof(LRUHandle) - 1 + key_length;
|
||||
// This is the size that is used when a new handle is created
|
||||
meta_charge += sizeof(LRUHandle) - 1 + key_length;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the memory usage by metadata.
|
||||
inline void CalcTotalCharge(
|
||||
size_t charge, CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
total_charge = charge + CalcuMetaCharge(metadata_charge_policy);
|
||||
}
|
||||
|
||||
inline size_t GetCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) const {
|
||||
size_t meta_charge = CalcuMetaCharge(metadata_charge_policy);
|
||||
assert(total_charge >= meta_charge);
|
||||
return total_charge - meta_charge;
|
||||
return charge + meta_charge;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -306,8 +256,6 @@ class LRUHandleTable {
|
||||
|
||||
int GetLengthBits() const { return length_bits_; }
|
||||
|
||||
size_t GetOccupancyCount() const { return elems_; }
|
||||
|
||||
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
|
||||
@@ -324,10 +272,10 @@ class LRUHandleTable {
|
||||
// a linked list of cache entries that hash into the bucket.
|
||||
std::unique_ptr<LRUHandle*[]> list_;
|
||||
|
||||
// Number of elements currently in the table.
|
||||
// Number of elements currently in the table
|
||||
uint32_t elems_;
|
||||
|
||||
// Set from max_upper_hash_bits (see constructor).
|
||||
// Set from max_upper_hash_bits (see constructor)
|
||||
const int max_length_bits_;
|
||||
};
|
||||
|
||||
@@ -335,8 +283,7 @@ class LRUHandleTable {
|
||||
class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
bool use_adaptive_mutex,
|
||||
double high_pri_pool_ratio, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache);
|
||||
@@ -344,7 +291,7 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
|
||||
// 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.
|
||||
// free the needed space
|
||||
virtual void SetCapacity(size_t capacity) override;
|
||||
|
||||
// Set the flag to reject insertion if cache if full.
|
||||
@@ -353,9 +300,6 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// Set percentage of capacity reserved for high-pri cache entries.
|
||||
void SetHighPriorityPoolRatio(double high_pri_pool_ratio);
|
||||
|
||||
// Set percentage of capacity reserved for low-pri cache entries.
|
||||
void SetLowPriorityPoolRatio(double low_pri_pool_ratio);
|
||||
|
||||
// Like Cache methods, but with an extra "hash" parameter.
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge, Cache::DeleterFn deleter,
|
||||
@@ -370,7 +314,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
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.
|
||||
// If helper_cb is null, the values of the following arguments don't
|
||||
// matter
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
const ShardedCache::CacheItemHelper* helper,
|
||||
const ShardedCache::CreateCallback& create_cb,
|
||||
@@ -381,14 +326,14 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
nullptr);
|
||||
}
|
||||
virtual bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool erase_if_last_ref) override {
|
||||
return Release(handle, erase_if_last_ref);
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
virtual bool IsReady(Cache::Handle* /*handle*/) override;
|
||||
virtual void Wait(Cache::Handle* /*handle*/) override {}
|
||||
virtual bool Ref(Cache::Handle* handle) override;
|
||||
virtual bool Release(Cache::Handle* handle,
|
||||
bool erase_if_last_ref = false) override;
|
||||
bool force_erase = false) override;
|
||||
virtual void Erase(const Slice& key, uint32_t hash) override;
|
||||
|
||||
// Although in some platforms the update of size_t is atomic, to make sure
|
||||
@@ -397,8 +342,6 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
|
||||
virtual size_t GetUsage() const override;
|
||||
virtual size_t GetPinnedUsage() const override;
|
||||
virtual size_t GetOccupancyCount() const override;
|
||||
virtual size_t GetTableAddressCount() const override;
|
||||
|
||||
virtual void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
@@ -409,33 +352,27 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
|
||||
virtual std::string GetPrintableOptions() const override;
|
||||
|
||||
void TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri,
|
||||
LRUHandle** lru_bottom_pri);
|
||||
void TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri);
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only.
|
||||
// Not threadsafe.
|
||||
// Retrieves number of elements in LRU, for unit test purpose only
|
||||
// not threadsafe
|
||||
size_t TEST_GetLRUSize();
|
||||
|
||||
// Retrieves high pri pool ratio
|
||||
// Retrieves high pri pool ratio
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
// Retrieves low pri pool ratio
|
||||
double GetLowPriPoolRatio();
|
||||
|
||||
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. If the cache is full
|
||||
// and free_handle_on_fail is true, the item is deleted and handle is set to
|
||||
// nullptr.
|
||||
// and free_handle_on_fail is true, the item is deleted and handle is set to.
|
||||
Status InsertItem(LRUHandle* item, Cache::Handle** 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,
|
||||
Cache::Handle** 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
|
||||
// Promote an item looked up from the secondary cache to the LRU cache. The
|
||||
// item 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.
|
||||
@@ -452,21 +389,15 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// 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_.
|
||||
// holding the mutex_
|
||||
void EvictFromLRU(size_t charge, autovector<LRUHandle*>* deleted);
|
||||
|
||||
// Try to insert the evicted handles into the secondary cache.
|
||||
void TryInsertIntoSecondaryCache(autovector<LRUHandle*> evicted_handles);
|
||||
|
||||
// Initialized before use.
|
||||
size_t capacity_;
|
||||
|
||||
// Memory size for entries in high-pri pool.
|
||||
size_t high_pri_pool_usage_;
|
||||
|
||||
// Memory size for entries in low-pri pool.
|
||||
size_t low_pri_pool_usage_;
|
||||
|
||||
// Whether to reject insertion if cache reaches its full capacity.
|
||||
bool strict_capacity_limit_;
|
||||
|
||||
@@ -477,13 +408,6 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// Remember the value to avoid recomputing each time.
|
||||
double high_pri_pool_capacity_;
|
||||
|
||||
// Ratio of capacity reserved for low priority cache entries.
|
||||
double low_pri_pool_ratio_;
|
||||
|
||||
// Low-pri pool size, equals to capacity * low_pri_pool_ratio.
|
||||
// Remember the value to avoid recomputing each time.
|
||||
double low_pri_pool_capacity_;
|
||||
|
||||
// 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
|
||||
@@ -492,9 +416,6 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// Pointer to head of low-pri pool in LRU list.
|
||||
LRUHandle* lru_low_pri_;
|
||||
|
||||
// Pointer to head of bottom-pri pool in LRU list.
|
||||
LRUHandle* lru_bottom_pri_;
|
||||
|
||||
// ------------^^^^^^^^^^^^^-----------
|
||||
// Not frequently modified data members
|
||||
// ------------------------------------
|
||||
@@ -508,16 +429,16 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// ------------vvvvvvvvvvvvv-----------
|
||||
LRUHandleTable table_;
|
||||
|
||||
// Memory size for entries residing in the cache.
|
||||
// Memory size for entries residing in the cache
|
||||
size_t usage_;
|
||||
|
||||
// Memory size for entries residing only in the LRU list.
|
||||
// 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_;
|
||||
mutable port::Mutex mutex_;
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
@@ -529,7 +450,7 @@ class LRUCache
|
||||
: public ShardedCache {
|
||||
public:
|
||||
LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, double low_pri_pool_ratio,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
@@ -545,11 +466,10 @@ class LRUCache
|
||||
virtual DeleterFn GetDeleter(Handle* handle) const override;
|
||||
virtual void DisownData() override;
|
||||
virtual void WaitAll(std::vector<Handle*>& handles) override;
|
||||
std::string GetPrintableOptions() const override;
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only.
|
||||
// Retrieves number of elements in LRU, for unit test purpose only
|
||||
size_t TEST_GetLRUSize();
|
||||
// Retrieves high pri pool ratio.
|
||||
// Retrieves high pri pool ratio
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
@@ -558,10 +478,4 @@ class LRUCache
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
} // namespace lru_cache
|
||||
|
||||
using LRUCache = lru_cache::LRUCache;
|
||||
using LRUHandle = lru_cache::LRUHandle;
|
||||
using LRUCacheShard = lru_cache::LRUCacheShard;
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+191
-1045
File diff suppressed because it is too large.
Load diff
Vendored
+6
-24
@@ -104,15 +104,14 @@ bool ShardedCache::Ref(Handle* handle) {
|
||||
return GetShard(Shard(hash))->Ref(handle);
|
||||
}
|
||||
|
||||
bool ShardedCache::Release(Handle* handle, bool erase_if_last_ref) {
|
||||
bool ShardedCache::Release(Handle* handle, bool force_erase) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->Release(handle, erase_if_last_ref);
|
||||
return GetShard(Shard(hash))->Release(handle, force_erase);
|
||||
}
|
||||
|
||||
bool ShardedCache::Release(Handle* handle, bool useful,
|
||||
bool erase_if_last_ref) {
|
||||
bool ShardedCache::Release(Handle* handle, bool useful, bool force_erase) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->Release(handle, useful, erase_if_last_ref);
|
||||
return GetShard(Shard(hash))->Release(handle, useful, force_erase);
|
||||
}
|
||||
|
||||
void ShardedCache::Erase(const Slice& key) {
|
||||
@@ -213,9 +212,9 @@ std::string ShardedCache::GetPrintableOptions() const {
|
||||
ret.append(GetShard(0)->GetPrintableOptions());
|
||||
return ret;
|
||||
}
|
||||
|
||||
int GetDefaultCacheShardBits(size_t capacity, size_t min_shard_size) {
|
||||
int GetDefaultCacheShardBits(size_t capacity) {
|
||||
int num_shard_bits = 0;
|
||||
size_t min_shard_size = 512L * 1024L; // Every shard is at least 512KB.
|
||||
size_t num_shards = capacity / min_shard_size;
|
||||
while (num_shards >>= 1) {
|
||||
if (++num_shard_bits >= 6) {
|
||||
@@ -230,21 +229,4 @@ int ShardedCache::GetNumShardBits() const { return BitsSetToOne(shard_mask_); }
|
||||
|
||||
uint32_t ShardedCache::GetNumShards() const { return shard_mask_ + 1; }
|
||||
|
||||
size_t ShardedCache::GetOccupancyCount() const {
|
||||
size_t oc = 0;
|
||||
uint32_t num_shards = GetNumShards();
|
||||
for (uint32_t s = 0; s < num_shards; s++) {
|
||||
oc += GetShard(s)->GetOccupancyCount();
|
||||
}
|
||||
return oc;
|
||||
}
|
||||
size_t ShardedCache::GetTableAddressCount() const {
|
||||
size_t tac = 0;
|
||||
uint32_t num_shards = GetNumShards();
|
||||
for (uint32_t s = 0; s < num_shards; s++) {
|
||||
tac += GetShard(s)->GetTableAddressCount();
|
||||
}
|
||||
return tac;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+12
-15
@@ -20,8 +20,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// Single cache shard interface.
|
||||
class CacheShard {
|
||||
public:
|
||||
explicit CacheShard(CacheMetadataChargePolicy metadata_charge_policy)
|
||||
: metadata_charge_policy_(metadata_charge_policy) {}
|
||||
CacheShard() = default;
|
||||
virtual ~CacheShard() = default;
|
||||
|
||||
using DeleterFn = Cache::DeleterFn;
|
||||
@@ -38,18 +37,16 @@ class CacheShard {
|
||||
Cache::Priority priority, bool wait,
|
||||
Statistics* stats) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool useful,
|
||||
bool erase_if_last_ref) = 0;
|
||||
bool force_erase) = 0;
|
||||
virtual bool IsReady(Cache::Handle* handle) = 0;
|
||||
virtual void Wait(Cache::Handle* handle) = 0;
|
||||
virtual bool Ref(Cache::Handle* handle) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool erase_if_last_ref) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool force_erase) = 0;
|
||||
virtual void Erase(const Slice& key, uint32_t hash) = 0;
|
||||
virtual void SetCapacity(size_t capacity) = 0;
|
||||
virtual void SetStrictCapacityLimit(bool strict_capacity_limit) = 0;
|
||||
virtual size_t GetUsage() const = 0;
|
||||
virtual size_t GetPinnedUsage() const = 0;
|
||||
virtual size_t GetOccupancyCount() const = 0;
|
||||
virtual size_t GetTableAddressCount() const = 0;
|
||||
// Handles iterating over roughly `average_entries_per_lock` entries, using
|
||||
// `state` to somehow record where it last ended up. Caller initially uses
|
||||
// *state == 0 and implementation sets *state = UINT32_MAX to indicate
|
||||
@@ -60,9 +57,13 @@ class CacheShard {
|
||||
uint32_t average_entries_per_lock, uint32_t* state) = 0;
|
||||
virtual void EraseUnRefEntries() = 0;
|
||||
virtual std::string GetPrintableOptions() const { return ""; }
|
||||
void set_metadata_charge_policy(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
metadata_charge_policy_ = metadata_charge_policy;
|
||||
}
|
||||
|
||||
protected:
|
||||
const CacheMetadataChargePolicy metadata_charge_policy_;
|
||||
CacheMetadataChargePolicy metadata_charge_policy_ = kDontChargeCacheMetadata;
|
||||
};
|
||||
|
||||
// Generic cache interface which shards cache by hash of keys. 2^num_shard_bits
|
||||
@@ -85,7 +86,7 @@ class ShardedCache : public Cache {
|
||||
DeleterFn deleter, Handle** handle,
|
||||
Priority priority) override;
|
||||
virtual Status Insert(const Slice& key, void* value,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
const CacheItemHelper* helper, size_t chargge,
|
||||
Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override;
|
||||
virtual Handle* Lookup(const Slice& key, Statistics* stats) override;
|
||||
@@ -93,11 +94,11 @@ class ShardedCache : public Cache {
|
||||
const CreateCallback& create_cb, Priority priority,
|
||||
bool wait, Statistics* stats = nullptr) override;
|
||||
virtual bool Release(Handle* handle, bool useful,
|
||||
bool erase_if_last_ref = false) override;
|
||||
bool force_erase = false) override;
|
||||
virtual bool IsReady(Handle* handle) override;
|
||||
virtual void Wait(Handle* handle) override;
|
||||
virtual bool Ref(Handle* handle) override;
|
||||
virtual bool Release(Handle* handle, bool erase_if_last_ref = false) override;
|
||||
virtual bool Release(Handle* handle, bool force_erase = false) override;
|
||||
virtual void Erase(const Slice& key) override;
|
||||
virtual uint64_t NewId() override;
|
||||
virtual size_t GetCapacity() const override;
|
||||
@@ -105,8 +106,6 @@ class ShardedCache : public Cache {
|
||||
virtual size_t GetUsage() const override;
|
||||
virtual size_t GetUsage(Handle* handle) const override;
|
||||
virtual size_t GetPinnedUsage() const override;
|
||||
virtual size_t GetOccupancyCount() const override;
|
||||
virtual size_t GetTableAddressCount() const override;
|
||||
virtual void ApplyToAllEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
@@ -128,8 +127,6 @@ class ShardedCache : public Cache {
|
||||
std::atomic<uint64_t> last_id_;
|
||||
};
|
||||
|
||||
// 512KB is traditional minimum shard size.
|
||||
int GetDefaultCacheShardBits(size_t capacity,
|
||||
size_t min_shard_size = 512U * 1024U);
|
||||
extern int GetDefaultCacheShardBits(size_t capacity);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,26 +0,0 @@
|
||||
# - Find liburing
|
||||
#
|
||||
# uring_INCLUDE_DIR - Where to find liburing.h
|
||||
# uring_LIBRARIES - List of libraries when using uring.
|
||||
# uring_FOUND - True if uring found.
|
||||
|
||||
find_path(uring_INCLUDE_DIR
|
||||
NAMES liburing.h)
|
||||
find_library(uring_LIBRARIES
|
||||
NAMES liburing.a liburing)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(uring
|
||||
DEFAULT_MSG uring_LIBRARIES uring_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(
|
||||
uring_INCLUDE_DIR
|
||||
uring_LIBRARIES)
|
||||
|
||||
if(uring_FOUND AND NOT TARGET uring::uring)
|
||||
add_library(uring::uring UNKNOWN IMPORTED)
|
||||
set_target_properties(uring::uring PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${uring_INCLUDE_DIR}"
|
||||
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
|
||||
IMPORTED_LOCATION "${uring_LIBRARIES}")
|
||||
endif()
|
||||
@@ -1,30 +0,0 @@
|
||||
ifndef PYTHON
|
||||
|
||||
# Default to python3. Some distros like CentOS 8 do not have `python`.
|
||||
ifeq ($(origin PYTHON), undefined)
|
||||
PYTHON := $(shell which python3 || which python || echo python3)
|
||||
endif
|
||||
export PYTHON
|
||||
|
||||
endif
|
||||
|
||||
# To setup tmp directory, first recognize some old variables for setting
|
||||
# test tmp directory or base tmp directory. TEST_TMPDIR is usually read
|
||||
# by RocksDB tools though Env/FileSystem::GetTestDirectory.
|
||||
ifeq ($(TEST_TMPDIR),)
|
||||
TEST_TMPDIR := $(TMPD)
|
||||
endif
|
||||
ifeq ($(TEST_TMPDIR),)
|
||||
ifeq ($(BASE_TMPDIR),)
|
||||
BASE_TMPDIR :=$(TMPDIR)
|
||||
endif
|
||||
ifeq ($(BASE_TMPDIR),)
|
||||
BASE_TMPDIR :=/tmp
|
||||
endif
|
||||
# Use /dev/shm if it has the sticky bit set (otherwise, /tmp or other
|
||||
# base dir), and create a randomly-named rocksdb.XXXX directory therein.
|
||||
TEST_TMPDIR := $(shell f=/dev/shm; test -k $$f || f=$(BASE_TMPDIR); \
|
||||
perl -le 'use File::Temp "tempdir";' \
|
||||
-e 'print tempdir("'$$f'/rocksdb.XXXX", CLEANUP => 0)')
|
||||
endif
|
||||
export TEST_TMPDIR
|
||||
@@ -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_platform009.sh
|
||||
source $ROOT/build_tools/fbcode_config_platform007.sh
|
||||
GCOV=$GCC_BASE/bin/gcov
|
||||
else
|
||||
GCOV=$(which gcov)
|
||||
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
# This file is used by Meta-internal infrastructure as well as by Makefile
|
||||
|
||||
# When included from Makefile, there are rules to build DB_STRESS_CMD. When
|
||||
# used directly with `make -f crashtest.mk ...` there will be no rules to
|
||||
# build DB_STRESS_CMD so it must exist prior.
|
||||
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)
|
||||
|
||||
.PHONY: crash_test crash_test_with_atomic_flush crash_test_with_txn \
|
||||
crash_test_with_best_efforts_recovery crash_test_with_ts \
|
||||
blackbox_crash_test blackbox_crash_test_with_atomic_flush \
|
||||
blackbox_crash_test_with_txn blackbox_crash_test_with_ts \
|
||||
blackbox_crash_test_with_best_efforts_recovery \
|
||||
whitebox_crash_test whitebox_crash_test_with_atomic_flush \
|
||||
whitebox_crash_test_with_txn whitebox_crash_test_with_ts \
|
||||
blackbox_crash_test_with_multiops_wc_txn \
|
||||
blackbox_crash_test_with_multiops_wp_txn \
|
||||
crash_test_with_tiered_storage blackbox_crash_test_with_tiered_storage \
|
||||
whitebox_crash_test_with_tiered_storage \
|
||||
|
||||
crash_test: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test
|
||||
|
||||
crash_test_with_atomic_flush: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_atomic_flush
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_atomic_flush
|
||||
|
||||
crash_test_with_txn: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_txn
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_txn
|
||||
|
||||
crash_test_with_best_efforts_recovery: blackbox_crash_test_with_best_efforts_recovery
|
||||
|
||||
crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_ts
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_ts
|
||||
|
||||
crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
# Do not parallelize
|
||||
$(CRASHTEST_MAKE) whitebox_crash_test_with_tiered_storage
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_tiered_storage
|
||||
|
||||
crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wc_txn
|
||||
|
||||
crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_MAKE) blackbox_crash_test_with_multiops_wp_txn
|
||||
|
||||
blackbox_crash_test: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(CRASHTEST_PY) blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_atomic_flush: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --cf_consistency blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_best_efforts_recovery: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_best_efforts_recovery blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --enable_ts blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_multiops_wc_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_multiops_txn --write_policy write_committed blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_multiops_wp_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_multiops_txn --write_policy write_prepared blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_tiered_storage: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --test_tiered_storage blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
|
||||
whitebox_crash_test: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --simple whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
$(CRASHTEST_PY) whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_atomic_flush: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --cf_consistency whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_txn: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_ts: $(DB_STRESS_CMD)
|
||||
$(CRASHTEST_PY) --enable_ts whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
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)
|
||||
@@ -23,7 +23,7 @@ Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
|
||||
if (prop_name == "rocksdb.iterator.super-version-number") {
|
||||
// First try to pass the value returned from inner iterator.
|
||||
if (!db_iter_->GetProperty(prop_name, prop).ok()) {
|
||||
*prop = std::to_string(sv_number_);
|
||||
*prop = ToString(sv_number_);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -45,7 +45,6 @@ void ArenaWrappedDBIter::Init(
|
||||
sv_number_ = version_number;
|
||||
read_options_ = read_options;
|
||||
allow_refresh_ = allow_refresh;
|
||||
memtable_range_tombstone_iter_ = nullptr;
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh() {
|
||||
@@ -59,7 +58,7 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
uint64_t cur_sv_number = cfd_->GetSuperVersionNumber();
|
||||
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:1");
|
||||
TEST_SYNC_POINT("ArenaWrappedDBIter::Refresh:2");
|
||||
auto reinit_internal_iter = [&]() {
|
||||
if (sv_number_ != cur_sv_number) {
|
||||
Env* env = db_iter_->env();
|
||||
db_iter_->~DBIter();
|
||||
arena_.~Arena();
|
||||
@@ -77,64 +76,12 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
|
||||
read_options_, cfd_, sv, &arena_, latest_seq,
|
||||
/* allow_unprepared_value */ true, /* db_iter */ this);
|
||||
read_options_, cfd_, sv, &arena_, db_iter_->GetRangeDelAggregator(),
|
||||
latest_seq, /* allow_unprepared_value */ true);
|
||||
SetIterUnderDBIter(internal_iter);
|
||||
};
|
||||
while (true) {
|
||||
if (sv_number_ != cur_sv_number) {
|
||||
reinit_internal_iter();
|
||||
break;
|
||||
} else {
|
||||
SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber();
|
||||
// Refresh range-tombstones in MemTable
|
||||
if (!read_options_.ignore_range_deletions) {
|
||||
SuperVersion* sv = cfd_->GetThreadLocalSuperVersion(db_impl_);
|
||||
TEST_SYNC_POINT_CALLBACK("ArenaWrappedDBIter::Refresh:SV", nullptr);
|
||||
auto t = sv->mem->NewRangeTombstoneIterator(
|
||||
read_options_, latest_seq, false /* immutable_memtable */);
|
||||
if (!t || t->empty()) {
|
||||
// If memtable_range_tombstone_iter_ points to a non-empty tombstone
|
||||
// iterator, then it means sv->mem is not the memtable that
|
||||
// memtable_range_tombstone_iter_ points to, so SV must have changed
|
||||
// after the sv_number_ != cur_sv_number check above. We will fall
|
||||
// back to re-init the InternalIterator, and the tombstone iterator
|
||||
// will be freed during db_iter destruction there.
|
||||
if (memtable_range_tombstone_iter_) {
|
||||
assert(!*memtable_range_tombstone_iter_ ||
|
||||
sv_number_ != cfd_->GetSuperVersionNumber());
|
||||
}
|
||||
delete t;
|
||||
} else { // current mutable memtable has range tombstones
|
||||
if (!memtable_range_tombstone_iter_) {
|
||||
delete t;
|
||||
db_impl_->ReturnAndCleanupSuperVersion(cfd_, sv);
|
||||
// The memtable under DBIter did not have range tombstone before
|
||||
// refresh.
|
||||
reinit_internal_iter();
|
||||
break;
|
||||
} else {
|
||||
delete *memtable_range_tombstone_iter_;
|
||||
*memtable_range_tombstone_iter_ = new TruncatedRangeDelIterator(
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator>(t),
|
||||
&cfd_->internal_comparator(), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
db_impl_->ReturnAndCleanupSuperVersion(cfd_, sv);
|
||||
}
|
||||
// Refresh latest sequence number
|
||||
db_iter_->set_sequence(latest_seq);
|
||||
db_iter_->set_valid(false);
|
||||
// Check again if the latest super version number is changed
|
||||
uint64_t latest_sv_number = cfd_->GetSuperVersionNumber();
|
||||
if (latest_sv_number != cur_sv_number) {
|
||||
// If the super version number is changed after refreshing,
|
||||
// fallback to Re-Init the InternalIterator
|
||||
cur_sv_number = latest_sv_number;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
db_iter_->set_sequence(db_impl_->GetLatestSequenceNumber());
|
||||
db_iter_->set_valid(false);
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -44,7 +44,9 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
// Get the arena to be used to allocate memory for DBIter to be wrapped,
|
||||
// as well as child iterators in it.
|
||||
virtual Arena* GetArena() { return &arena_; }
|
||||
|
||||
virtual ReadRangeDelAggregator* GetRangeDelAggregator() {
|
||||
return db_iter_->GetRangeDelAggregator();
|
||||
}
|
||||
const ReadOptions& GetReadOptions() { return read_options_; }
|
||||
|
||||
// Set the internal iterator wrapped inside the DB Iterator. Usually it is
|
||||
@@ -53,10 +55,6 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
db_iter_->SetIter(iter);
|
||||
}
|
||||
|
||||
void SetMemtableRangetombstoneIter(TruncatedRangeDelIterator** iter) {
|
||||
memtable_range_tombstone_iter_ = iter;
|
||||
}
|
||||
|
||||
bool Valid() const override { return db_iter_->Valid(); }
|
||||
void SeekToFirst() override { db_iter_->SeekToFirst(); }
|
||||
void SeekToLast() override { db_iter_->SeekToLast(); }
|
||||
@@ -70,7 +68,6 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
void Prev() override { db_iter_->Prev(); }
|
||||
Slice key() const override { return db_iter_->key(); }
|
||||
Slice value() const override { return db_iter_->value(); }
|
||||
const WideColumns& columns() const override { return db_iter_->columns(); }
|
||||
Status status() const override { return db_iter_->status(); }
|
||||
Slice timestamp() const override { return db_iter_->timestamp(); }
|
||||
bool IsBlob() const { return db_iter_->IsBlob(); }
|
||||
@@ -107,9 +104,6 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
ReadCallback* read_callback_;
|
||||
bool expose_blob_index_ = false;
|
||||
bool allow_refresh_ = true;
|
||||
// If this is nullptr, it means the mutable memtable does not contain range
|
||||
// tombstone when added under this DBIter.
|
||||
TruncatedRangeDelIterator** memtable_range_tombstone_iter_ = nullptr;
|
||||
};
|
||||
|
||||
// Generate the arena wrapped iterator class.
|
||||
|
||||
@@ -1,90 +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 "db/blob/blob_contents.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "port/malloc.h"
|
||||
|
||||
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;
|
||||
|
||||
if (allocation_) {
|
||||
MemoryAllocator* const allocator = allocation_.get_deleter().allocator;
|
||||
|
||||
if (allocator) {
|
||||
usage += allocator->UsableSize(allocation_.get(), data_.size());
|
||||
} else {
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
usage += malloc_usable_size(allocation_.get());
|
||||
#else
|
||||
usage += data_.size();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ROCKSDB_MALLOC_USABLE_SIZE
|
||||
usage += malloc_usable_size(const_cast<BlobContents*>(this));
|
||||
#else
|
||||
usage += sizeof(*this);
|
||||
#endif
|
||||
|
||||
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
|
||||
@@ -1,56 +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 <memory>
|
||||
|
||||
#include "memory/memory_allocator.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A class representing a single uncompressed value read from a blob file.
|
||||
class BlobContents {
|
||||
public:
|
||||
static std::unique_ptr<BlobContents> Create(CacheAllocationPtr&& allocation,
|
||||
size_t size);
|
||||
|
||||
BlobContents(const BlobContents&) = delete;
|
||||
BlobContents& operator=(const BlobContents&) = delete;
|
||||
|
||||
BlobContents(BlobContents&&) = default;
|
||||
BlobContents& operator=(BlobContents&&) = default;
|
||||
|
||||
~BlobContents() = default;
|
||||
|
||||
const Slice& data() const { return data_; }
|
||||
size_t size() const { return data_.size(); }
|
||||
|
||||
size_t ApproximateMemoryUsage() const;
|
||||
|
||||
// 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_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+8
-20
@@ -9,26 +9,14 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFetcher::FetchBlob(const Slice& user_key,
|
||||
const Slice& blob_index_slice,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
PinnableSlice* blob_value,
|
||||
uint64_t* bytes_read) const {
|
||||
Status BlobFetcher::FetchBlob(const Slice& user_key, const Slice& blob_index,
|
||||
PinnableSlice* blob_value) {
|
||||
Status s;
|
||||
assert(version_);
|
||||
|
||||
return version_->GetBlob(read_options_, user_key, blob_index_slice,
|
||||
prefetch_buffer, blob_value, bytes_read);
|
||||
constexpr uint64_t* bytes_read = nullptr;
|
||||
s = version_->GetBlob(read_options_, user_key, blob_index, blob_value,
|
||||
bytes_read);
|
||||
return s;
|
||||
}
|
||||
|
||||
Status BlobFetcher::FetchBlob(const Slice& user_key,
|
||||
const BlobIndex& blob_index,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
PinnableSlice* blob_value,
|
||||
uint64_t* bytes_read) const {
|
||||
assert(version_);
|
||||
|
||||
return version_->GetBlob(read_options_, user_key, blob_index, prefetch_buffer,
|
||||
blob_value, bytes_read);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+5
-16
@@ -9,29 +9,18 @@
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Version;
|
||||
class Slice;
|
||||
class FilePrefetchBuffer;
|
||||
class PinnableSlice;
|
||||
class BlobIndex;
|
||||
|
||||
// A thin wrapper around the blob retrieval functionality of Version.
|
||||
class BlobFetcher {
|
||||
public:
|
||||
BlobFetcher(const Version* version, const ReadOptions& read_options)
|
||||
BlobFetcher(Version* version, const ReadOptions& read_options)
|
||||
: version_(version), read_options_(read_options) {}
|
||||
|
||||
Status FetchBlob(const Slice& user_key, const Slice& blob_index_slice,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
PinnableSlice* blob_value, uint64_t* bytes_read) const;
|
||||
|
||||
Status FetchBlob(const Slice& user_key, const BlobIndex& blob_index,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
PinnableSlice* blob_value, uint64_t* bytes_read) const;
|
||||
Status FetchBlob(const Slice& user_key, const Slice& blob_index,
|
||||
PinnableSlice* blob_value);
|
||||
|
||||
private:
|
||||
const Version* version_;
|
||||
Version* version_;
|
||||
ReadOptions read_options_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "db/blob/blob_contents.h"
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
@@ -33,9 +32,9 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
VersionSet* versions, FileSystem* fs,
|
||||
const ImmutableOptions* immutable_options,
|
||||
const MutableCFOptions* mutable_cf_options, const FileOptions* file_options,
|
||||
std::string db_id, std::string db_session_id, int job_id,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
Env::IOPriority io_priority, Env::WriteLifeTimeHint write_hint,
|
||||
int job_id, uint32_t column_family_id,
|
||||
const std::string& column_family_name, Env::IOPriority io_priority,
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
@@ -43,18 +42,17 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs,
|
||||
immutable_options, mutable_cf_options, file_options,
|
||||
db_id, db_session_id, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint, io_tracer,
|
||||
blob_callback, creation_reason, blob_file_paths,
|
||||
blob_file_additions) {}
|
||||
job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, io_tracer, blob_callback, creation_reason,
|
||||
blob_file_paths, blob_file_additions) {}
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
std::function<uint64_t()> file_number_generator, FileSystem* fs,
|
||||
const ImmutableOptions* immutable_options,
|
||||
const MutableCFOptions* mutable_cf_options, const FileOptions* file_options,
|
||||
std::string db_id, std::string db_session_id, int job_id,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
Env::IOPriority io_priority, Env::WriteLifeTimeHint write_hint,
|
||||
int job_id, uint32_t column_family_id,
|
||||
const std::string& column_family_name, Env::IOPriority io_priority,
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
@@ -66,10 +64,7 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
min_blob_size_(mutable_cf_options->min_blob_size),
|
||||
blob_file_size_(mutable_cf_options->blob_file_size),
|
||||
blob_compression_type_(mutable_cf_options->blob_compression_type),
|
||||
prepopulate_blob_cache_(mutable_cf_options->prepopulate_blob_cache),
|
||||
file_options_(file_options),
|
||||
db_id_(std::move(db_id)),
|
||||
db_session_id_(std::move(db_session_id)),
|
||||
job_id_(job_id),
|
||||
column_family_id_(column_family_id),
|
||||
column_family_name_(column_family_name),
|
||||
@@ -138,16 +133,6 @@ Status BlobFileBuilder::Add(const Slice& key, const Slice& value,
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const Status s =
|
||||
PutBlobIntoCacheIfNeeded(value, blob_file_number, blob_offset);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_options_->info_log,
|
||||
"Failed to pre-populate the blob into blob cache: %s",
|
||||
s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
BlobIndex::EncodeBlob(blob_index, blob_file_number, blob_offset, blob.size(),
|
||||
blob_compression_type_);
|
||||
|
||||
@@ -387,60 +372,4 @@ void BlobFileBuilder::Abandon(const Status& s) {
|
||||
blob_count_ = 0;
|
||||
blob_bytes_ = 0;
|
||||
}
|
||||
|
||||
Status BlobFileBuilder::PutBlobIntoCacheIfNeeded(const Slice& blob,
|
||||
uint64_t blob_file_number,
|
||||
uint64_t blob_offset) const {
|
||||
Status s = Status::OK();
|
||||
|
||||
auto blob_cache = immutable_options_->blob_cache;
|
||||
auto statistics = immutable_options_->statistics.get();
|
||||
bool warm_cache =
|
||||
prepopulate_blob_cache_ == PrepopulateBlobCache::kFlushOnly &&
|
||||
creation_reason_ == BlobFileCreationReason::kFlush;
|
||||
|
||||
if (blob_cache && warm_cache) {
|
||||
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_,
|
||||
blob_file_number);
|
||||
const CacheKey cache_key = base_cache_key.WithOffset(blob_offset);
|
||||
const Slice key = cache_key.AsSlice();
|
||||
|
||||
const Cache::Priority priority = Cache::Priority::BOTTOM;
|
||||
|
||||
// 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, buf->size());
|
||||
buf.release();
|
||||
} else {
|
||||
RecordTick(statistics, BLOB_DB_CACHE_ADD_FAILURES);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -10,7 +10,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/advanced_options.h"
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
@@ -36,8 +35,7 @@ class BlobFileBuilder {
|
||||
BlobFileBuilder(VersionSet* versions, FileSystem* fs,
|
||||
const ImmutableOptions* immutable_options,
|
||||
const MutableCFOptions* mutable_cf_options,
|
||||
const FileOptions* file_options, std::string db_id,
|
||||
std::string db_session_id, int job_id,
|
||||
const FileOptions* file_options, int job_id,
|
||||
uint32_t column_family_id,
|
||||
const std::string& column_family_name,
|
||||
Env::IOPriority io_priority,
|
||||
@@ -51,8 +49,7 @@ class BlobFileBuilder {
|
||||
BlobFileBuilder(std::function<uint64_t()> file_number_generator,
|
||||
FileSystem* fs, const ImmutableOptions* immutable_options,
|
||||
const MutableCFOptions* mutable_cf_options,
|
||||
const FileOptions* file_options, std::string db_id,
|
||||
std::string db_session_id, int job_id,
|
||||
const FileOptions* file_options, int job_id,
|
||||
uint32_t column_family_id,
|
||||
const std::string& column_family_name,
|
||||
Env::IOPriority io_priority,
|
||||
@@ -81,19 +78,13 @@ class BlobFileBuilder {
|
||||
Status CloseBlobFile();
|
||||
Status CloseBlobFileIfNeeded();
|
||||
|
||||
Status PutBlobIntoCacheIfNeeded(const Slice& blob, uint64_t blob_file_number,
|
||||
uint64_t blob_offset) const;
|
||||
|
||||
std::function<uint64_t()> file_number_generator_;
|
||||
FileSystem* fs_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
uint64_t min_blob_size_;
|
||||
uint64_t blob_file_size_;
|
||||
CompressionType blob_compression_type_;
|
||||
PrepopulateBlobCache prepopulate_blob_cache_;
|
||||
const FileOptions* file_options_;
|
||||
const std::string db_id_;
|
||||
const std::string db_session_id_;
|
||||
int job_id_;
|
||||
uint32_t column_family_id_;
|
||||
std::string column_family_name_;
|
||||
|
||||
@@ -144,9 +144,8 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, "" /*db_id*/, "" /*db_session_id*/, job_id,
|
||||
column_family_id, column_family_name, io_priority, write_hint,
|
||||
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
@@ -229,9 +228,8 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, "" /*db_id*/, "" /*db_session_id*/, job_id,
|
||||
column_family_id, column_family_name, io_priority, write_hint,
|
||||
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
@@ -317,9 +315,8 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, "" /*db_id*/, "" /*db_session_id*/, job_id,
|
||||
column_family_id, column_family_name, io_priority, write_hint,
|
||||
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) {
|
||||
@@ -372,9 +369,8 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, "" /*db_id*/, "" /*db_session_id*/, job_id,
|
||||
column_family_id, column_family_name, io_priority, write_hint,
|
||||
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
@@ -456,9 +452,8 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, "" /*db_id*/, "" /*db_session_id*/, job_id,
|
||||
column_family_id, column_family_name, io_priority, write_hint,
|
||||
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue",
|
||||
@@ -536,9 +531,8 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, "" /*db_id*/, "" /*db_session_id*/, job_id,
|
||||
column_family_id, column_family_name, io_priority, write_hint,
|
||||
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
@@ -634,9 +628,8 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, "" /*db_id*/, "" /*db_session_id*/, job_id,
|
||||
column_family_id, column_family_name, io_priority, write_hint,
|
||||
nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) {
|
||||
|
||||
@@ -254,7 +254,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(blob_file_cache.GetBlobFileReader(blob_file_number, &reader)
|
||||
.IsMemoryLimit());
|
||||
.IsIncomplete());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
|
||||
+100
-144
@@ -8,16 +8,13 @@
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_contents.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/file_prefetch_buffer.h"
|
||||
#include "file/filename.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/multiget_context.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/crc32c.h"
|
||||
@@ -150,10 +147,9 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
constexpr uint64_t read_offset = 0;
|
||||
constexpr size_t read_size = BlobLogHeader::kSize;
|
||||
|
||||
// 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 */);
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_offset, read_size, statistics,
|
||||
&header_slice, &buf, &aligned_buf);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -201,10 +197,9 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
const uint64_t read_offset = file_size - BlobLogFooter::kSize;
|
||||
constexpr size_t read_size = BlobLogFooter::kSize;
|
||||
|
||||
// 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 */);
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_offset, read_size, statistics,
|
||||
&footer_slice, &buf, &aligned_buf);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -234,8 +229,7 @@ Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Statistics* statistics, Slice* slice,
|
||||
Buffer* buf, AlignedBuf* aligned_buf,
|
||||
Env::IOPriority rate_limiter_priority) {
|
||||
Buffer* buf, AlignedBuf* aligned_buf) {
|
||||
assert(slice);
|
||||
assert(buf);
|
||||
assert(aligned_buf);
|
||||
@@ -250,13 +244,13 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
constexpr char* scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(IOOptions(), read_offset, read_size, slice, scratch,
|
||||
aligned_buf, rate_limiter_priority);
|
||||
aligned_buf);
|
||||
} else {
|
||||
buf->reset(new char[read_size]);
|
||||
constexpr AlignedBuf* aligned_scratch = nullptr;
|
||||
|
||||
s = file_reader->Read(IOOptions(), read_offset, read_size, slice,
|
||||
buf->get(), aligned_scratch, rate_limiter_priority);
|
||||
buf->get(), aligned_scratch);
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
@@ -284,12 +278,13 @@ BlobFileReader::BlobFileReader(
|
||||
|
||||
BlobFileReader::~BlobFileReader() = default;
|
||||
|
||||
Status BlobFileReader::GetBlob(
|
||||
const ReadOptions& read_options, const Slice& user_key, uint64_t offset,
|
||||
uint64_t value_size, CompressionType compression_type,
|
||||
FilePrefetchBuffer* prefetch_buffer, MemoryAllocator* allocator,
|
||||
std::unique_ptr<BlobContents>* result, uint64_t* bytes_read) const {
|
||||
assert(result);
|
||||
Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
const Slice& user_key, uint64_t offset,
|
||||
uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
PinnableSlice* value,
|
||||
uint64_t* bytes_read) const {
|
||||
assert(value);
|
||||
|
||||
const uint64_t key_size = user_key.size();
|
||||
|
||||
@@ -318,37 +313,19 @@ Status BlobFileReader::GetBlob(
|
||||
Buffer buf;
|
||||
AlignedBuf aligned_buf;
|
||||
|
||||
bool prefetched = false;
|
||||
|
||||
if (prefetch_buffer) {
|
||||
Status s;
|
||||
constexpr bool for_compaction = true;
|
||||
|
||||
prefetched = prefetch_buffer->TryReadFromCache(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!prefetched) {
|
||||
{
|
||||
TEST_SYNC_POINT("BlobFileReader::GetBlob:ReadFromFile");
|
||||
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(), record_offset,
|
||||
static_cast<size_t>(record_size), statistics_,
|
||||
&record_slice, &buf, &aligned_buf,
|
||||
read_options.rate_limiter_priority);
|
||||
&record_slice, &buf, &aligned_buf);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("BlobFileReader::GetBlob:TamperWithResult",
|
||||
&record_slice);
|
||||
TEST_SYNC_POINT_CALLBACK("BlobFileReader::GetBlob:TamperWithResult",
|
||||
&record_slice);
|
||||
}
|
||||
|
||||
if (read_options.verify_checksums) {
|
||||
const Status s = VerifyBlob(record_slice, user_key, value_size);
|
||||
@@ -360,8 +337,8 @@ Status BlobFileReader::GetBlob(
|
||||
const Slice value_slice(record_slice.data() + adjustment, value_size);
|
||||
|
||||
{
|
||||
const Status s = UncompressBlobIfNeeded(
|
||||
value_slice, compression_type, allocator, clock_, statistics_, result);
|
||||
const Status s = UncompressBlobIfNeeded(value_slice, compression_type,
|
||||
clock_, statistics_, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -375,56 +352,39 @@ Status BlobFileReader::GetBlob(
|
||||
}
|
||||
|
||||
void BlobFileReader::MultiGetBlob(
|
||||
const ReadOptions& read_options, MemoryAllocator* allocator,
|
||||
autovector<std::pair<BlobReadRequest*, std::unique_ptr<BlobContents>>>&
|
||||
blob_reqs,
|
||||
uint64_t* bytes_read) const {
|
||||
const size_t num_blobs = blob_reqs.size();
|
||||
const ReadOptions& read_options,
|
||||
const autovector<std::reference_wrapper<const Slice>>& user_keys,
|
||||
const autovector<uint64_t>& offsets,
|
||||
const autovector<uint64_t>& value_sizes, autovector<Status*>& statuses,
|
||||
autovector<PinnableSlice*>& values, uint64_t* bytes_read) const {
|
||||
const size_t num_blobs = user_keys.size();
|
||||
assert(num_blobs > 0);
|
||||
assert(num_blobs <= MultiGetContext::MAX_BATCH_SIZE);
|
||||
assert(num_blobs == offsets.size());
|
||||
assert(num_blobs == value_sizes.size());
|
||||
assert(num_blobs == statuses.size());
|
||||
assert(num_blobs == values.size());
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < num_blobs - 1; ++i) {
|
||||
assert(blob_reqs[i].first->offset <= blob_reqs[i + 1].first->offset);
|
||||
for (size_t i = 0; i < offsets.size() - 1; ++i) {
|
||||
assert(offsets[i] <= offsets[i + 1]);
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
|
||||
std::vector<FSReadRequest> read_reqs;
|
||||
std::vector<FSReadRequest> read_reqs(num_blobs);
|
||||
autovector<uint64_t> adjustments;
|
||||
uint64_t total_len = 0;
|
||||
read_reqs.reserve(num_blobs);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
BlobReadRequest* const req = blob_reqs[i].first;
|
||||
assert(req);
|
||||
assert(req->user_key);
|
||||
assert(req->status);
|
||||
|
||||
const size_t key_size = req->user_key->size();
|
||||
const uint64_t offset = req->offset;
|
||||
const uint64_t value_size = req->len;
|
||||
|
||||
if (!IsValidBlobOffset(offset, key_size, value_size, file_size_)) {
|
||||
*req->status = Status::Corruption("Invalid blob offset");
|
||||
continue;
|
||||
}
|
||||
if (req->compression != compression_type_) {
|
||||
*req->status =
|
||||
Status::Corruption("Compression type mismatch when reading a blob");
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t key_size = user_keys[i].get().size();
|
||||
assert(IsValidBlobOffset(offsets[i], key_size, value_sizes[i], file_size_));
|
||||
const uint64_t adjustment =
|
||||
read_options.verify_checksums
|
||||
? BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size)
|
||||
: 0;
|
||||
assert(req->offset >= adjustment);
|
||||
assert(offsets[i] >= adjustment);
|
||||
adjustments.push_back(adjustment);
|
||||
|
||||
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[i].offset = offsets[i] - adjustment;
|
||||
read_reqs[i].len = value_sizes[i] + adjustment;
|
||||
total_len += read_reqs[i].len;
|
||||
}
|
||||
|
||||
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, total_len);
|
||||
@@ -447,81 +407,70 @@ 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);
|
||||
s = file_reader_->MultiRead(IOOptions(), read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr,
|
||||
read_options.rate_limiter_priority);
|
||||
direct_io ? &aligned_buf : nullptr);
|
||||
if (!s.ok()) {
|
||||
for (auto& req : read_reqs) {
|
||||
req.status.PermitUncheckedError();
|
||||
}
|
||||
for (auto& blob_req : blob_reqs) {
|
||||
BlobReadRequest* const req = blob_req.first;
|
||||
assert(req);
|
||||
assert(req->status);
|
||||
|
||||
if (!req->status->IsCorruption()) {
|
||||
// Avoid overwriting corruption status.
|
||||
*req->status = s;
|
||||
}
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
*statuses[i] = s;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(s.ok());
|
||||
|
||||
uint64_t total_bytes = 0;
|
||||
for (size_t i = 0, j = 0; i < num_blobs; ++i) {
|
||||
BlobReadRequest* const req = blob_reqs[i].first;
|
||||
assert(req);
|
||||
assert(req->user_key);
|
||||
assert(req->status);
|
||||
|
||||
if (!req->status->ok()) {
|
||||
continue;
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
auto& req = read_reqs[i];
|
||||
assert(statuses[i]);
|
||||
if (req.status.ok() && req.result.size() != req.len) {
|
||||
req.status = IOStatus::Corruption("Failed to read data from blob file");
|
||||
}
|
||||
*statuses[i] = req.status;
|
||||
}
|
||||
|
||||
assert(j < read_reqs.size());
|
||||
auto& read_req = read_reqs[j++];
|
||||
const auto& record_slice = read_req.result;
|
||||
if (read_req.status.ok() && record_slice.size() != read_req.len) {
|
||||
read_req.status =
|
||||
IOStatus::Corruption("Failed to read data from blob file");
|
||||
}
|
||||
|
||||
*req->status = read_req.status;
|
||||
if (!req->status->ok()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Verify checksums if enabled
|
||||
if (read_options.verify_checksums) {
|
||||
*req->status = VerifyBlob(record_slice, *req->user_key, req->len);
|
||||
if (!req->status->ok()) {
|
||||
if (read_options.verify_checksums) {
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
if (!statuses[i]->ok()) {
|
||||
continue;
|
||||
}
|
||||
const Slice& record_slice = read_reqs[i].result;
|
||||
s = VerifyBlob(record_slice, user_keys[i], value_sizes[i]);
|
||||
if (!s.ok()) {
|
||||
assert(statuses[i]);
|
||||
*statuses[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Uncompress blob if needed
|
||||
Slice value_slice(record_slice.data() + adjustments[i], req->len);
|
||||
*req->status =
|
||||
UncompressBlobIfNeeded(value_slice, compression_type_, allocator,
|
||||
clock_, statistics_, &blob_reqs[i].second);
|
||||
if (req->status->ok()) {
|
||||
total_bytes += record_slice.size();
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
if (!statuses[i]->ok()) {
|
||||
continue;
|
||||
}
|
||||
const Slice& record_slice = read_reqs[i].result;
|
||||
const Slice value_slice(record_slice.data() + adjustments[i],
|
||||
value_sizes[i]);
|
||||
s = UncompressBlobIfNeeded(value_slice, compression_type_, clock_,
|
||||
statistics_, values[i]);
|
||||
if (!s.ok()) {
|
||||
*statuses[i] = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_read) {
|
||||
uint64_t total_bytes = 0;
|
||||
for (const auto& req : read_reqs) {
|
||||
total_bytes += req.result.size();
|
||||
}
|
||||
*bytes_read = total_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
Status BlobFileReader::VerifyBlob(const Slice& record_slice,
|
||||
const Slice& user_key, uint64_t value_size) {
|
||||
PERF_TIMER_GUARD(blob_checksum_time);
|
||||
|
||||
BlobLogRecord record;
|
||||
|
||||
const Slice header_slice(record_slice.data(), BlobLogRecord::kHeaderSize);
|
||||
@@ -562,18 +511,15 @@ Status BlobFileReader::VerifyBlob(const Slice& record_slice,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobFileReader::UncompressBlobIfNeeded(
|
||||
const Slice& value_slice, CompressionType compression_type,
|
||||
MemoryAllocator* allocator, SystemClock* clock, Statistics* statistics,
|
||||
std::unique_ptr<BlobContents>* result) {
|
||||
assert(result);
|
||||
Status BlobFileReader::UncompressBlobIfNeeded(const Slice& value_slice,
|
||||
CompressionType compression_type,
|
||||
SystemClock* clock,
|
||||
Statistics* statistics,
|
||||
PinnableSlice* value) {
|
||||
assert(value);
|
||||
|
||||
if (compression_type == kNoCompression) {
|
||||
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());
|
||||
SaveValue(value_slice, value);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -584,11 +530,11 @@ Status BlobFileReader::UncompressBlobIfNeeded(
|
||||
|
||||
size_t uncompressed_size = 0;
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
constexpr MemoryAllocator* allocator = nullptr;
|
||||
|
||||
CacheAllocationPtr output;
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(blob_decompress_time);
|
||||
StopWatch stop_watch(clock, statistics, BLOB_DB_DECOMPRESSION_MICROS);
|
||||
output = UncompressData(info, value_slice.data(), value_slice.size(),
|
||||
&uncompressed_size, compression_format_version,
|
||||
@@ -602,9 +548,19 @@ Status BlobFileReader::UncompressBlobIfNeeded(
|
||||
return Status::Corruption("Unable to uncompress blob");
|
||||
}
|
||||
|
||||
*result = BlobContents::Create(std::move(output), uncompressed_size);
|
||||
SaveValue(Slice(output.get(), uncompressed_size), value);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFileReader::SaveValue(const Slice& src, PinnableSlice* dst) {
|
||||
assert(dst);
|
||||
|
||||
if (dst->IsPinned()) {
|
||||
dst->Reset();
|
||||
}
|
||||
|
||||
dst->PinSelf(src);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+11
-15
@@ -8,7 +8,6 @@
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#include "db/blob/blob_read_request.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
@@ -22,8 +21,7 @@ struct FileOptions;
|
||||
class HistogramImpl;
|
||||
struct ReadOptions;
|
||||
class Slice;
|
||||
class FilePrefetchBuffer;
|
||||
class BlobContents;
|
||||
class PinnableSlice;
|
||||
class Statistics;
|
||||
|
||||
class BlobFileReader {
|
||||
@@ -43,18 +41,16 @@ class BlobFileReader {
|
||||
|
||||
Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
|
||||
uint64_t offset, uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
MemoryAllocator* allocator,
|
||||
std::unique_ptr<BlobContents>* result,
|
||||
CompressionType compression_type, PinnableSlice* value,
|
||||
uint64_t* bytes_read) const;
|
||||
|
||||
// offsets must be sorted in ascending order by caller.
|
||||
void MultiGetBlob(
|
||||
const ReadOptions& read_options, MemoryAllocator* allocator,
|
||||
autovector<std::pair<BlobReadRequest*, std::unique_ptr<BlobContents>>>&
|
||||
blob_reqs,
|
||||
uint64_t* bytes_read) const;
|
||||
const ReadOptions& read_options,
|
||||
const autovector<std::reference_wrapper<const Slice>>& user_keys,
|
||||
const autovector<uint64_t>& offsets,
|
||||
const autovector<uint64_t>& value_sizes, autovector<Status*>& statuses,
|
||||
autovector<PinnableSlice*>& values, uint64_t* bytes_read) const;
|
||||
|
||||
CompressionType GetCompressionType() const { return compression_type_; }
|
||||
|
||||
@@ -85,18 +81,18 @@ class BlobFileReader {
|
||||
static Status ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Statistics* statistics, Slice* slice, Buffer* buf,
|
||||
AlignedBuf* aligned_buf,
|
||||
Env::IOPriority rate_limiter_priority);
|
||||
AlignedBuf* aligned_buf);
|
||||
|
||||
static Status VerifyBlob(const Slice& record_slice, const Slice& user_key,
|
||||
uint64_t value_size);
|
||||
|
||||
static Status UncompressBlobIfNeeded(const Slice& value_slice,
|
||||
CompressionType compression_type,
|
||||
MemoryAllocator* allocator,
|
||||
SystemClock* clock,
|
||||
Statistics* statistics,
|
||||
std::unique_ptr<BlobContents>* result);
|
||||
PinnableSlice* value);
|
||||
|
||||
static void SaveValue(const Slice& src, PinnableSlice* dst);
|
||||
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader_;
|
||||
uint64_t file_size_;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_contents.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
#include "env/mock_env.h"
|
||||
@@ -180,44 +179,37 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr MemoryAllocator* allocator = nullptr;
|
||||
|
||||
{
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, keys[0], blob_offsets[0],
|
||||
blob_sizes[0], kNoCompression, prefetch_buffer,
|
||||
allocator, &value, &bytes_read));
|
||||
ASSERT_NE(value, nullptr);
|
||||
ASSERT_EQ(value->data(), blobs[0]);
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read));
|
||||
ASSERT_EQ(value, blobs[0]);
|
||||
ASSERT_EQ(bytes_read, blob_sizes[0]);
|
||||
|
||||
// MultiGetBlob
|
||||
bytes_read = 0;
|
||||
size_t total_size = 0;
|
||||
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
std::array<BlobReadRequest, num_blobs> requests_buf;
|
||||
autovector<std::pair<BlobReadRequest*, std::unique_ptr<BlobContents>>>
|
||||
blob_reqs;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
requests_buf[i] =
|
||||
BlobReadRequest(keys[i], blob_offsets[i], blob_sizes[i],
|
||||
kNoCompression, nullptr, &statuses_buf[i]);
|
||||
blob_reqs.emplace_back(&requests_buf[i], std::unique_ptr<BlobContents>());
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
|
||||
reader->MultiGetBlob(read_options, allocator, blob_reqs, &bytes_read);
|
||||
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
const auto& result = blob_reqs[i].second;
|
||||
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_EQ(result->data(), blobs[i]);
|
||||
ASSERT_EQ(value_buf[i], blobs[i]);
|
||||
total_size += blob_sizes[i];
|
||||
}
|
||||
ASSERT_EQ(bytes_read, total_size);
|
||||
@@ -226,14 +218,13 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
{
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, keys[1], blob_offsets[1],
|
||||
blob_sizes[1], kNoCompression, prefetch_buffer,
|
||||
allocator, &value, &bytes_read));
|
||||
ASSERT_NE(value, nullptr);
|
||||
ASSERT_EQ(value->data(), blobs[1]);
|
||||
blob_sizes[1], kNoCompression, &value,
|
||||
&bytes_read));
|
||||
ASSERT_EQ(value, blobs[1]);
|
||||
|
||||
const uint64_t key_size = keys[1].size();
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -243,60 +234,55 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
// Invalid offset (too close to start of file)
|
||||
{
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, keys[0], blob_offsets[0] - 1,
|
||||
blob_sizes[0], kNoCompression, prefetch_buffer,
|
||||
allocator, &value, &bytes_read)
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Invalid offset (too close to end of file)
|
||||
{
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, keys[2], blob_offsets[2] + 1,
|
||||
blob_sizes[2], kNoCompression, prefetch_buffer,
|
||||
allocator, &value, &bytes_read)
|
||||
blob_sizes[2], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect compression type
|
||||
{
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, keys[0], blob_offsets[0],
|
||||
blob_sizes[0], kZSTD, prefetch_buffer, allocator,
|
||||
&value, &bytes_read)
|
||||
blob_sizes[0], kZSTD, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect key size
|
||||
{
|
||||
constexpr char shorter_key[] = "k";
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, shorter_key,
|
||||
blob_offsets[0] -
|
||||
(keys[0].size() - sizeof(shorter_key) + 1),
|
||||
blob_sizes[0], kNoCompression, prefetch_buffer,
|
||||
allocator, &value, &bytes_read)
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
@@ -311,21 +297,15 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
blob_offsets[0],
|
||||
blob_offsets[1] - (keys[1].size() - key_refs[1].get().size()),
|
||||
blob_offsets[2]};
|
||||
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
std::array<BlobReadRequest, num_blobs> requests_buf;
|
||||
autovector<std::pair<BlobReadRequest*, std::unique_ptr<BlobContents>>>
|
||||
blob_reqs;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
requests_buf[i] =
|
||||
BlobReadRequest(key_refs[i], offsets[i], blob_sizes[i],
|
||||
kNoCompression, nullptr, &statuses_buf[i]);
|
||||
blob_reqs.emplace_back(&requests_buf[i], std::unique_ptr<BlobContents>());
|
||||
}
|
||||
|
||||
reader->MultiGetBlob(read_options, allocator, blob_reqs, &bytes_read);
|
||||
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i == 1) {
|
||||
ASSERT_TRUE(statuses_buf[i].IsCorruption());
|
||||
@@ -338,15 +318,14 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
// Incorrect key
|
||||
{
|
||||
constexpr char incorrect_key[] = "foo1";
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, incorrect_key, blob_offsets[0],
|
||||
blob_sizes[0], kNoCompression, prefetch_buffer,
|
||||
allocator, &value, &bytes_read)
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
@@ -357,20 +336,17 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
Slice wrong_key_slice(incorrect_key, sizeof(incorrect_key) - 1);
|
||||
key_refs[2] = std::cref(wrong_key_slice);
|
||||
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
std::array<BlobReadRequest, num_blobs> requests_buf;
|
||||
autovector<std::pair<BlobReadRequest*, std::unique_ptr<BlobContents>>>
|
||||
blob_reqs;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
requests_buf[i] =
|
||||
BlobReadRequest(key_refs[i], blob_offsets[i], blob_sizes[i],
|
||||
kNoCompression, nullptr, &statuses_buf[i]);
|
||||
blob_reqs.emplace_back(&requests_buf[i], std::unique_ptr<BlobContents>());
|
||||
}
|
||||
|
||||
reader->MultiGetBlob(read_options, allocator, blob_reqs, &bytes_read);
|
||||
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i == num_blobs - 1) {
|
||||
ASSERT_TRUE(statuses_buf[i].IsCorruption());
|
||||
@@ -382,15 +358,14 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
// Incorrect value size
|
||||
{
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, keys[1], blob_offsets[1],
|
||||
blob_sizes[1] + 1, kNoCompression,
|
||||
prefetch_buffer, allocator, &value, &bytes_read)
|
||||
blob_sizes[1] + 1, kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
@@ -398,29 +373,17 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1] + 1, blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
std::array<BlobReadRequest, num_blobs> requests_buf;
|
||||
|
||||
requests_buf[0] =
|
||||
BlobReadRequest(key_refs[0], blob_offsets[0], blob_sizes[0],
|
||||
kNoCompression, nullptr, &statuses_buf[0]);
|
||||
requests_buf[1] =
|
||||
BlobReadRequest(key_refs[1], blob_offsets[1], blob_sizes[1] + 1,
|
||||
kNoCompression, nullptr, &statuses_buf[1]);
|
||||
requests_buf[2] =
|
||||
BlobReadRequest(key_refs[2], blob_offsets[2], blob_sizes[2],
|
||||
kNoCompression, nullptr, &statuses_buf[2]);
|
||||
|
||||
autovector<std::pair<BlobReadRequest*, std::unique_ptr<BlobContents>>>
|
||||
blob_reqs;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
blob_reqs.emplace_back(&requests_buf[i], std::unique_ptr<BlobContents>());
|
||||
}
|
||||
|
||||
reader->MultiGetBlob(read_options, allocator, blob_reqs, &bytes_read);
|
||||
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i != 1) {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
@@ -679,18 +642,13 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr MemoryAllocator* allocator = nullptr;
|
||||
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, prefetch_buffer, allocator, &value,
|
||||
&bytes_read)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -737,32 +695,25 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = false;
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr MemoryAllocator* allocator = nullptr;
|
||||
|
||||
{
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kSnappyCompression, prefetch_buffer, allocator,
|
||||
&value, &bytes_read));
|
||||
ASSERT_NE(value, nullptr);
|
||||
ASSERT_EQ(value->data(), blob);
|
||||
kSnappyCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_EQ(bytes_read, blob_size);
|
||||
}
|
||||
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
{
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kSnappyCompression, prefetch_buffer, allocator,
|
||||
&value, &bytes_read));
|
||||
ASSERT_NE(value, nullptr);
|
||||
ASSERT_EQ(value->data(), blob);
|
||||
kSnappyCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
|
||||
constexpr uint64_t key_size = sizeof(key) - 1;
|
||||
ASSERT_EQ(bytes_read,
|
||||
@@ -819,18 +770,13 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr MemoryAllocator* allocator = nullptr;
|
||||
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kSnappyCompression, prefetch_buffer, allocator,
|
||||
&value, &bytes_read)
|
||||
kSnappyCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -908,18 +854,13 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr MemoryAllocator* allocator = nullptr;
|
||||
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, prefetch_buffer, allocator,
|
||||
&value, &bytes_read)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsIOError());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
@@ -996,18 +937,13 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
} else {
|
||||
ASSERT_OK(s);
|
||||
|
||||
constexpr FilePrefetchBuffer* prefetch_buffer = nullptr;
|
||||
constexpr MemoryAllocator* allocator = nullptr;
|
||||
|
||||
std::unique_ptr<BlobContents> value;
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, prefetch_buffer, allocator,
|
||||
&value, &bytes_read)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(value, nullptr);
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,13 +92,13 @@ class BlobIndex {
|
||||
}
|
||||
|
||||
Status DecodeFrom(Slice slice) {
|
||||
const char* kErrorMessage = "Error while decoding blob index";
|
||||
static const std::string kErrorMessage = "Error while decoding blob index";
|
||||
assert(slice.size() > 0);
|
||||
type_ = static_cast<Type>(*slice.data());
|
||||
if (type_ >= Type::kUnknown) {
|
||||
return Status::Corruption(kErrorMessage,
|
||||
"Unknown blob index type: " +
|
||||
std::to_string(static_cast<char>(type_)));
|
||||
return Status::Corruption(
|
||||
kErrorMessage,
|
||||
"Unknown blob index type: " + ToString(static_cast<char>(type_)));
|
||||
}
|
||||
slice = Slice(slice.data() + 1, slice.size() - 1);
|
||||
if (HasTTL()) {
|
||||
|
||||
@@ -26,7 +26,8 @@ void BlobLogHeader::EncodeTo(std::string* dst) {
|
||||
}
|
||||
|
||||
Status BlobLogHeader::DecodeFrom(Slice src) {
|
||||
const char* kErrorMessage = "Error while decoding blob log header";
|
||||
static const std::string kErrorMessage =
|
||||
"Error while decoding blob log header";
|
||||
if (src.size() != BlobLogHeader::kSize) {
|
||||
return Status::Corruption(kErrorMessage,
|
||||
"Unexpected blob file header size");
|
||||
@@ -70,7 +71,8 @@ void BlobLogFooter::EncodeTo(std::string* dst) {
|
||||
}
|
||||
|
||||
Status BlobLogFooter::DecodeFrom(Slice src) {
|
||||
const char* kErrorMessage = "Error while decoding blob log footer";
|
||||
static const std::string kErrorMessage =
|
||||
"Error while decoding blob log footer";
|
||||
if (src.size() != BlobLogFooter::kSize) {
|
||||
return Status::Corruption(kErrorMessage,
|
||||
"Unexpected blob file footer size");
|
||||
@@ -110,7 +112,7 @@ void BlobLogRecord::EncodeHeaderTo(std::string* dst) {
|
||||
}
|
||||
|
||||
Status BlobLogRecord::DecodeHeaderFrom(Slice src) {
|
||||
const char* kErrorMessage = "Error while decoding blob record";
|
||||
static const std::string kErrorMessage = "Error while decoding blob record";
|
||||
if (src.size() != BlobLogRecord::kHeaderSize) {
|
||||
return Status::Corruption(kErrorMessage,
|
||||
"Unexpected blob record header size");
|
||||
|
||||
@@ -28,10 +28,8 @@ Status BlobLogSequentialReader::ReadSlice(uint64_t size, Slice* slice,
|
||||
assert(file_);
|
||||
|
||||
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, Env::IO_TOTAL /* rate_limiter_priority */);
|
||||
Status s = file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size),
|
||||
slice, buf, nullptr);
|
||||
next_byte_ += size;
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
|
||||
+19
-25
@@ -70,38 +70,32 @@ Status BlobLogWriter::AppendFooter(BlobLogFooter& footer,
|
||||
std::string str;
|
||||
footer.EncodeTo(&str);
|
||||
|
||||
Status s;
|
||||
if (dest_->seen_error()) {
|
||||
s.PermitUncheckedError();
|
||||
return Status::IOError("Seen Error. Skip closing.");
|
||||
} else {
|
||||
s = dest_->Append(Slice(str));
|
||||
if (s.ok()) {
|
||||
block_offset_ += str.size();
|
||||
Status s = dest_->Append(Slice(str));
|
||||
if (s.ok()) {
|
||||
block_offset_ += str.size();
|
||||
|
||||
s = Sync();
|
||||
s = Sync();
|
||||
|
||||
if (s.ok()) {
|
||||
s = dest_->Close();
|
||||
|
||||
if (s.ok()) {
|
||||
s = dest_->Close();
|
||||
assert(!!checksum_method == !!checksum_value);
|
||||
|
||||
if (s.ok()) {
|
||||
assert(!!checksum_method == !!checksum_value);
|
||||
if (checksum_method) {
|
||||
assert(checksum_method->empty());
|
||||
|
||||
if (checksum_method) {
|
||||
assert(checksum_method->empty());
|
||||
|
||||
std::string method = dest_->GetFileChecksumFuncName();
|
||||
if (method != kUnknownFileChecksumFuncName) {
|
||||
*checksum_method = std::move(method);
|
||||
}
|
||||
std::string method = dest_->GetFileChecksumFuncName();
|
||||
if (method != kUnknownFileChecksumFuncName) {
|
||||
*checksum_method = std::move(method);
|
||||
}
|
||||
if (checksum_value) {
|
||||
assert(checksum_value->empty());
|
||||
}
|
||||
if (checksum_value) {
|
||||
assert(checksum_value->empty());
|
||||
|
||||
std::string value = dest_->GetFileChecksum();
|
||||
if (value != kUnknownFileChecksum) {
|
||||
*checksum_value = std::move(value);
|
||||
}
|
||||
std::string value = dest_->GetFileChecksum();
|
||||
if (value != kUnknownFileChecksum) {
|
||||
*checksum_value = std::move(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +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 <cinttypes>
|
||||
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A read Blob request structure for use in BlobSource::MultiGetBlob and
|
||||
// BlobFileReader::MultiGetBlob.
|
||||
struct BlobReadRequest {
|
||||
// User key to lookup the paired blob
|
||||
const Slice* user_key = nullptr;
|
||||
|
||||
// File offset in bytes
|
||||
uint64_t offset = 0;
|
||||
|
||||
// Length to read in bytes
|
||||
size_t len = 0;
|
||||
|
||||
// Blob compression type
|
||||
CompressionType compression = kNoCompression;
|
||||
|
||||
// Output parameter set by MultiGetBlob() to point to the data buffer, and
|
||||
// the number of valid bytes
|
||||
PinnableSlice* result = nullptr;
|
||||
|
||||
// Status of read
|
||||
Status* status = nullptr;
|
||||
|
||||
BlobReadRequest(const Slice& _user_key, uint64_t _offset, size_t _len,
|
||||
CompressionType _compression, PinnableSlice* _result,
|
||||
Status* _status)
|
||||
: user_key(&_user_key),
|
||||
offset(_offset),
|
||||
len(_len),
|
||||
compression(_compression),
|
||||
result(_result),
|
||||
status(_status) {}
|
||||
|
||||
BlobReadRequest() = default;
|
||||
BlobReadRequest(const BlobReadRequest& other) = default;
|
||||
BlobReadRequest& operator=(const BlobReadRequest& other) = default;
|
||||
};
|
||||
|
||||
using BlobFileReadRequests =
|
||||
std::tuple<uint64_t /* file_number */, uint64_t /* file_size */,
|
||||
autovector<BlobReadRequest>>;
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,488 +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 "db/blob/blob_source.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
#include "cache/charged_cache.h"
|
||||
#include "db/blob/blob_contents.h"
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "table/get_context.h"
|
||||
#include "table/multiget_context.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobSource::BlobSource(const ImmutableOptions* immutable_options,
|
||||
const std::string& db_id,
|
||||
const std::string& db_session_id,
|
||||
BlobFileCache* blob_file_cache)
|
||||
: db_id_(db_id),
|
||||
db_session_id_(db_session_id),
|
||||
statistics_(immutable_options->statistics.get()),
|
||||
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_ = std::make_shared<ChargedCache>(immutable_options->blob_cache,
|
||||
bbto->block_cache);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
BlobSource::~BlobSource() = default;
|
||||
|
||||
Status BlobSource::GetBlobFromCache(
|
||||
const Slice& cache_key, CacheHandleGuard<BlobContents>* cached_blob) const {
|
||||
assert(blob_cache_);
|
||||
assert(!cache_key.empty());
|
||||
assert(cached_blob);
|
||||
assert(cached_blob->IsEmpty());
|
||||
|
||||
Cache::Handle* cache_handle = nullptr;
|
||||
cache_handle = GetEntryFromCache(cache_key);
|
||||
if (cache_handle != nullptr) {
|
||||
*cached_blob =
|
||||
CacheHandleGuard<BlobContents>(blob_cache_.get(), cache_handle);
|
||||
|
||||
assert(cached_blob->GetValue());
|
||||
|
||||
PERF_COUNTER_ADD(blob_cache_hit_count, 1);
|
||||
RecordTick(statistics_, BLOB_DB_CACHE_HIT);
|
||||
RecordTick(statistics_, BLOB_DB_CACHE_BYTES_READ,
|
||||
cached_blob->GetValue()->size());
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
RecordTick(statistics_, BLOB_DB_CACHE_MISS);
|
||||
|
||||
return Status::NotFound("Blob not found in cache");
|
||||
}
|
||||
|
||||
Status BlobSource::PutBlobIntoCache(
|
||||
const Slice& cache_key, std::unique_ptr<BlobContents>* blob,
|
||||
CacheHandleGuard<BlobContents>* cached_blob) const {
|
||||
assert(blob_cache_);
|
||||
assert(!cache_key.empty());
|
||||
assert(blob);
|
||||
assert(*blob);
|
||||
assert(cached_blob);
|
||||
assert(cached_blob->IsEmpty());
|
||||
|
||||
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();
|
||||
|
||||
assert(cache_handle != nullptr);
|
||||
*cached_blob =
|
||||
CacheHandleGuard<BlobContents>(blob_cache_.get(), cache_handle);
|
||||
|
||||
assert(cached_blob->GetValue());
|
||||
|
||||
RecordTick(statistics_, BLOB_DB_CACHE_ADD);
|
||||
RecordTick(statistics_, BLOB_DB_CACHE_BYTES_WRITE,
|
||||
cached_blob->GetValue()->size());
|
||||
|
||||
} else {
|
||||
RecordTick(statistics_, BLOB_DB_CACHE_ADD_FAILURES);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
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,
|
||||
PinnableSlice* value) {
|
||||
assert(cached_blob);
|
||||
assert(cached_blob->GetValue());
|
||||
assert(value);
|
||||
|
||||
// To avoid copying the cached blob into the buffer provided by the
|
||||
// application, we can simply transfer ownership of the cache handle to
|
||||
// the target PinnableSlice. This has the potential to save a lot of
|
||||
// CPU, especially with large blob values.
|
||||
|
||||
value->Reset();
|
||||
|
||||
constexpr Cleanable* cleanable = nullptr;
|
||||
value->PinSlice(cached_blob->GetValue()->data(), cleanable);
|
||||
|
||||
cached_blob->TransferTo(value);
|
||||
}
|
||||
|
||||
void BlobSource::PinOwnedBlob(std::unique_ptr<BlobContents>* owned_blob,
|
||||
PinnableSlice* value) {
|
||||
assert(owned_blob);
|
||||
assert(*owned_blob);
|
||||
assert(value);
|
||||
|
||||
BlobContents* const blob = owned_blob->release();
|
||||
assert(blob);
|
||||
|
||||
value->Reset();
|
||||
value->PinSlice(
|
||||
blob->data(),
|
||||
[](void* arg1, void* /* arg2 */) {
|
||||
delete static_cast<BlobContents*>(arg1);
|
||||
},
|
||||
blob, nullptr);
|
||||
}
|
||||
|
||||
Status BlobSource::InsertEntryIntoCache(const Slice& key, BlobContents* value,
|
||||
size_t charge,
|
||||
Cache::Handle** cache_handle,
|
||||
Cache::Priority priority) const {
|
||||
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,
|
||||
const Slice& user_key, uint64_t file_number,
|
||||
uint64_t offset, uint64_t file_size,
|
||||
uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
FilePrefetchBuffer* prefetch_buffer,
|
||||
PinnableSlice* value, uint64_t* bytes_read) {
|
||||
assert(value);
|
||||
|
||||
Status s;
|
||||
|
||||
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
|
||||
|
||||
CacheHandleGuard<BlobContents> blob_handle;
|
||||
|
||||
// First, try to get the blob from the cache
|
||||
//
|
||||
// If blob cache is enabled, we'll try to read from it.
|
||||
if (blob_cache_) {
|
||||
Slice key = cache_key.AsSlice();
|
||||
s = GetBlobFromCache(key, &blob_handle);
|
||||
if (s.ok()) {
|
||||
PinCachedBlob(&blob_handle, value);
|
||||
|
||||
// For consistency, the size of on-disk (possibly compressed) blob record
|
||||
// is assigned to bytes_read.
|
||||
uint64_t adjustment =
|
||||
read_options.verify_checksums
|
||||
? BlobLogRecord::CalculateAdjustmentForRecordHeader(
|
||||
user_key.size())
|
||||
: 0;
|
||||
assert(offset >= adjustment);
|
||||
|
||||
uint64_t record_size = value_size + adjustment;
|
||||
if (bytes_read) {
|
||||
*bytes_read = record_size;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
assert(blob_handle.IsEmpty());
|
||||
|
||||
const bool no_io = read_options.read_tier == kBlockCacheTier;
|
||||
if (no_io) {
|
||||
s = Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
|
||||
return s;
|
||||
}
|
||||
|
||||
// Can't find the blob from the cache. Since I/O is allowed, read from the
|
||||
// file.
|
||||
std::unique_ptr<BlobContents> blob_contents;
|
||||
|
||||
{
|
||||
CacheHandleGuard<BlobFileReader> blob_file_reader;
|
||||
s = blob_file_cache_->GetBlobFileReader(file_number, &blob_file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
assert(blob_file_reader.GetValue());
|
||||
|
||||
if (compression_type != blob_file_reader.GetValue()->GetCompressionType()) {
|
||||
return Status::Corruption("Compression type mismatch when reading blob");
|
||||
}
|
||||
|
||||
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(
|
||||
read_options, user_key, offset, value_size, compression_type,
|
||||
prefetch_buffer, allocator, &blob_contents, &read_size);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
if (bytes_read) {
|
||||
*bytes_read = read_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (blob_cache_ && read_options.fill_cache) {
|
||||
// If filling cache is allowed and a cache is configured, try to put the
|
||||
// blob to the cache.
|
||||
Slice key = cache_key.AsSlice();
|
||||
s = PutBlobIntoCache(key, &blob_contents, &blob_handle);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
PinCachedBlob(&blob_handle, value);
|
||||
} else {
|
||||
PinOwnedBlob(&blob_contents, value);
|
||||
}
|
||||
|
||||
assert(s.ok());
|
||||
return s;
|
||||
}
|
||||
|
||||
void BlobSource::MultiGetBlob(const ReadOptions& read_options,
|
||||
autovector<BlobFileReadRequests>& blob_reqs,
|
||||
uint64_t* bytes_read) {
|
||||
assert(blob_reqs.size() > 0);
|
||||
|
||||
uint64_t total_bytes_read = 0;
|
||||
uint64_t bytes_read_in_file = 0;
|
||||
|
||||
for (auto& [file_number, file_size, blob_reqs_in_file] : blob_reqs) {
|
||||
// sort blob_reqs_in_file by file offset.
|
||||
std::sort(
|
||||
blob_reqs_in_file.begin(), blob_reqs_in_file.end(),
|
||||
[](const BlobReadRequest& lhs, const BlobReadRequest& rhs) -> bool {
|
||||
return lhs.offset < rhs.offset;
|
||||
});
|
||||
|
||||
MultiGetBlobFromOneFile(read_options, file_number, file_size,
|
||||
blob_reqs_in_file, &bytes_read_in_file);
|
||||
|
||||
total_bytes_read += bytes_read_in_file;
|
||||
}
|
||||
|
||||
if (bytes_read) {
|
||||
*bytes_read = total_bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
void BlobSource::MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
uint64_t file_number,
|
||||
uint64_t /*file_size*/,
|
||||
autovector<BlobReadRequest>& blob_reqs,
|
||||
uint64_t* bytes_read) {
|
||||
const size_t num_blobs = blob_reqs.size();
|
||||
assert(num_blobs > 0);
|
||||
assert(num_blobs <= MultiGetContext::MAX_BATCH_SIZE);
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < num_blobs - 1; ++i) {
|
||||
assert(blob_reqs[i].offset <= blob_reqs[i + 1].offset);
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
|
||||
using Mask = uint64_t;
|
||||
Mask cache_hit_mask = 0;
|
||||
|
||||
uint64_t total_bytes = 0;
|
||||
const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
|
||||
|
||||
if (blob_cache_) {
|
||||
size_t cached_blob_count = 0;
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
auto& req = blob_reqs[i];
|
||||
|
||||
CacheHandleGuard<BlobContents> blob_handle;
|
||||
const CacheKey cache_key = base_cache_key.WithOffset(req.offset);
|
||||
const Slice key = cache_key.AsSlice();
|
||||
|
||||
const Status s = GetBlobFromCache(key, &blob_handle);
|
||||
|
||||
if (s.ok()) {
|
||||
assert(req.status);
|
||||
*req.status = s;
|
||||
|
||||
PinCachedBlob(&blob_handle, req.result);
|
||||
|
||||
// Update the counter for the number of valid blobs read from the cache.
|
||||
++cached_blob_count;
|
||||
|
||||
// For consistency, the size of each on-disk (possibly compressed) blob
|
||||
// record is accumulated to total_bytes.
|
||||
uint64_t adjustment =
|
||||
read_options.verify_checksums
|
||||
? BlobLogRecord::CalculateAdjustmentForRecordHeader(
|
||||
req.user_key->size())
|
||||
: 0;
|
||||
assert(req.offset >= adjustment);
|
||||
total_bytes += req.len + adjustment;
|
||||
cache_hit_mask |= (Mask{1} << i); // cache hit
|
||||
}
|
||||
}
|
||||
|
||||
// All blobs were read from the cache.
|
||||
if (cached_blob_count == num_blobs) {
|
||||
if (bytes_read) {
|
||||
*bytes_read = total_bytes;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const bool no_io = read_options.read_tier == kBlockCacheTier;
|
||||
if (no_io) {
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (!(cache_hit_mask & (Mask{1} << i))) {
|
||||
BlobReadRequest& req = blob_reqs[i];
|
||||
assert(req.status);
|
||||
|
||||
*req.status =
|
||||
Status::Incomplete("Cannot read blob(s): no disk I/O allowed");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
// Find the rest of blobs from the file since I/O is allowed.
|
||||
autovector<std::pair<BlobReadRequest*, std::unique_ptr<BlobContents>>>
|
||||
_blob_reqs;
|
||||
uint64_t _bytes_read = 0;
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (!(cache_hit_mask & (Mask{1} << i))) {
|
||||
_blob_reqs.emplace_back(&blob_reqs[i], std::unique_ptr<BlobContents>());
|
||||
}
|
||||
}
|
||||
|
||||
CacheHandleGuard<BlobFileReader> 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;
|
||||
assert(req);
|
||||
assert(req->status);
|
||||
|
||||
*req->status = s;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(blob_file_reader.GetValue());
|
||||
|
||||
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);
|
||||
|
||||
if (blob_cache_ && read_options.fill_cache) {
|
||||
// If filling cache is allowed and a cache is configured, try to put
|
||||
// the blob(s) to the cache.
|
||||
for (auto& [req, blob_contents] : _blob_reqs) {
|
||||
assert(req);
|
||||
|
||||
if (req->status->ok()) {
|
||||
CacheHandleGuard<BlobContents> blob_handle;
|
||||
const CacheKey cache_key = base_cache_key.WithOffset(req->offset);
|
||||
const Slice key = cache_key.AsSlice();
|
||||
s = PutBlobIntoCache(key, &blob_contents, &blob_handle);
|
||||
if (!s.ok()) {
|
||||
*req->status = s;
|
||||
} else {
|
||||
PinCachedBlob(&blob_handle, req->result);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto& [req, blob_contents] : _blob_reqs) {
|
||||
assert(req);
|
||||
|
||||
if (req->status->ok()) {
|
||||
PinOwnedBlob(&blob_contents, req->result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total_bytes += _bytes_read;
|
||||
if (bytes_read) {
|
||||
*bytes_read = total_bytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BlobSource::TEST_BlobInCache(uint64_t file_number, uint64_t file_size,
|
||||
uint64_t offset, size_t* charge) const {
|
||||
const CacheKey cache_key = GetCacheKey(file_number, file_size, offset);
|
||||
const Slice key = cache_key.AsSlice();
|
||||
|
||||
CacheHandleGuard<BlobContents> blob_handle;
|
||||
const Status s = GetBlobFromCache(key, &blob_handle);
|
||||
|
||||
if (s.ok() && blob_handle.GetValue() != nullptr) {
|
||||
if (charge) {
|
||||
const Cache* const cache = blob_handle.GetCache();
|
||||
assert(cache);
|
||||
|
||||
Cache::Handle* const handle = blob_handle.GetCacheHandle();
|
||||
assert(handle);
|
||||
|
||||
*charge = cache->GetUsage(handle);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,153 +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 <cinttypes>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "cache/cache_key.h"
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/blob/blob_read_request.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
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.
|
||||
// Depending on user settings, it always fetch blobs from multi-tier cache and
|
||||
// storage with minimal cost.
|
||||
class BlobSource {
|
||||
public:
|
||||
BlobSource(const ImmutableOptions* immutable_options,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
BlobFileCache* blob_file_cache);
|
||||
|
||||
BlobSource(const BlobSource&) = delete;
|
||||
BlobSource& operator=(const BlobSource&) = delete;
|
||||
|
||||
~BlobSource();
|
||||
|
||||
// Read a blob from the underlying cache or one blob file.
|
||||
//
|
||||
// If successful, returns ok and sets "*value" to the newly retrieved
|
||||
// uncompressed blob. If there was an error while fetching the blob, sets
|
||||
// "*value" to empty and returns a non-ok status.
|
||||
//
|
||||
// Note: For consistency, whether the blob is found in the cache or on disk,
|
||||
// sets "*bytes_read" to the size of on-disk (possibly compressed) blob
|
||||
// record.
|
||||
Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
|
||||
uint64_t file_number, uint64_t offset, uint64_t file_size,
|
||||
uint64_t value_size, CompressionType compression_type,
|
||||
FilePrefetchBuffer* prefetch_buffer, PinnableSlice* value,
|
||||
uint64_t* bytes_read);
|
||||
|
||||
// Read multiple blobs from the underlying cache or blob file(s).
|
||||
//
|
||||
// If successful, returns ok and sets "result" in the elements of "blob_reqs"
|
||||
// to the newly retrieved uncompressed blobs. If there was an error while
|
||||
// fetching one of blobs, sets its "result" to empty and sets its
|
||||
// corresponding "status" to a non-ok status.
|
||||
//
|
||||
// Note:
|
||||
// - The main difference between this function and MultiGetBlobFromOneFile is
|
||||
// that this function can read multiple blobs from multiple blob files.
|
||||
//
|
||||
// - For consistency, whether the blob is found in the cache or on disk, sets
|
||||
// "*bytes_read" to the total size of on-disk (possibly compressed) blob
|
||||
// records.
|
||||
void MultiGetBlob(const ReadOptions& read_options,
|
||||
autovector<BlobFileReadRequests>& blob_reqs,
|
||||
uint64_t* bytes_read);
|
||||
|
||||
// Read multiple blobs from the underlying cache or one blob file.
|
||||
//
|
||||
// If successful, returns ok and sets "result" in the elements of "blob_reqs"
|
||||
// to the newly retrieved uncompressed blobs. If there was an error while
|
||||
// fetching one of blobs, sets its "result" to empty and sets its
|
||||
// corresponding "status" to a non-ok status.
|
||||
//
|
||||
// Note:
|
||||
// - The main difference between this function and MultiGetBlob is that this
|
||||
// function is only used for the case where the demanded blobs are stored in
|
||||
// one blob file. MultiGetBlob will call this function multiple times if the
|
||||
// demanded blobs are stored in multiple blob files.
|
||||
//
|
||||
// - For consistency, whether the blob is found in the cache or on disk, sets
|
||||
// "*bytes_read" to the total size of on-disk (possibly compressed) blob
|
||||
// records.
|
||||
void MultiGetBlobFromOneFile(const ReadOptions& read_options,
|
||||
uint64_t file_number, uint64_t file_size,
|
||||
autovector<BlobReadRequest>& blob_reqs,
|
||||
uint64_t* bytes_read);
|
||||
|
||||
inline Status GetBlobFileReader(
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
return blob_file_cache_->GetBlobFileReader(blob_file_number,
|
||||
blob_file_reader);
|
||||
}
|
||||
|
||||
inline Cache* GetBlobCache() const { return blob_cache_.get(); }
|
||||
|
||||
bool TEST_BlobInCache(uint64_t file_number, uint64_t file_size,
|
||||
uint64_t offset, size_t* charge = nullptr) const;
|
||||
|
||||
private:
|
||||
Status GetBlobFromCache(const Slice& cache_key,
|
||||
CacheHandleGuard<BlobContents>* cached_blob) const;
|
||||
|
||||
Status PutBlobIntoCache(const Slice& cache_key,
|
||||
std::unique_ptr<BlobContents>* blob,
|
||||
CacheHandleGuard<BlobContents>* cached_blob) const;
|
||||
|
||||
static void PinCachedBlob(CacheHandleGuard<BlobContents>* cached_blob,
|
||||
PinnableSlice* value);
|
||||
|
||||
static void PinOwnedBlob(std::unique_ptr<BlobContents>* owned_blob,
|
||||
PinnableSlice* value);
|
||||
|
||||
Cache::Handle* GetEntryFromCache(const Slice& key) const;
|
||||
|
||||
Status InsertEntryIntoCache(const Slice& key, BlobContents* value,
|
||||
size_t charge, Cache::Handle** cache_handle,
|
||||
Cache::Priority priority) const;
|
||||
|
||||
inline CacheKey GetCacheKey(uint64_t file_number, uint64_t /*file_size*/,
|
||||
uint64_t offset) const {
|
||||
OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
|
||||
return base_cache_key.WithOffset(offset);
|
||||
}
|
||||
|
||||
const std::string& db_id_;
|
||||
const std::string& db_session_id_;
|
||||
|
||||
Statistics* statistics_;
|
||||
|
||||
// A cache to store blob file reader.
|
||||
BlobFileCache* blob_file_cache_;
|
||||
|
||||
// A cache to store uncompressed blobs.
|
||||
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
|
||||
// isn't strictly speaking a non-volatile tier since the compressed cache in
|
||||
// this tier is in volatile memory).
|
||||
const CacheTier lowest_used_cache_tier_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
File diff suppressed because it is too large.
Load diff
+37
-792
@@ -5,9 +5,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "cache/compressed_secondary_cache.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/db_test_util.h"
|
||||
@@ -50,326 +48,6 @@ TEST_F(DBBlobBasicTest, GetBlob) {
|
||||
.IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlobFromCache) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 2 << 20; // 2MB
|
||||
co.num_shard_bits = 2;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
auto backing_cache = NewLRUCache(co);
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.blob_cache = backing_cache;
|
||||
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.no_block_cache = false;
|
||||
block_based_options.block_cache = backing_cache;
|
||||
block_based_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_based_options));
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
|
||||
read_options.fill_cache = false;
|
||||
|
||||
{
|
||||
PinnableSlice result;
|
||||
|
||||
read_options.read_tier = kReadAllTier;
|
||||
ASSERT_OK(db_->Get(read_options, db_->DefaultColumnFamily(), key, &result));
|
||||
ASSERT_EQ(result, blob_value);
|
||||
|
||||
result.Reset();
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
// Try again with no I/O allowed. Since we didn't re-fill the cache, the
|
||||
// blob itself can only be read from the blob file, so the read should
|
||||
// return Incomplete.
|
||||
ASSERT_TRUE(db_->Get(read_options, db_->DefaultColumnFamily(), key, &result)
|
||||
.IsIncomplete());
|
||||
ASSERT_TRUE(result.empty());
|
||||
}
|
||||
|
||||
read_options.fill_cache = true;
|
||||
|
||||
{
|
||||
PinnableSlice result;
|
||||
|
||||
read_options.read_tier = kReadAllTier;
|
||||
ASSERT_OK(db_->Get(read_options, db_->DefaultColumnFamily(), key, &result));
|
||||
ASSERT_EQ(result, blob_value);
|
||||
|
||||
result.Reset();
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
// Try again with no I/O allowed. The table and the necessary blocks/blobs
|
||||
// should already be in their respective caches.
|
||||
ASSERT_OK(db_->Get(read_options, db_->DefaultColumnFamily(), key, &result));
|
||||
ASSERT_EQ(result, blob_value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, IterateBlobsFromCache) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 2 << 20; // 2MB
|
||||
co.num_shard_bits = 2;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
auto backing_cache = NewLRUCache(co);
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.blob_cache = backing_cache;
|
||||
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.no_block_cache = false;
|
||||
block_based_options.block_cache = backing_cache;
|
||||
block_based_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_based_options));
|
||||
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
Reopen(options);
|
||||
|
||||
int num_blobs = 5;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::string> blobs;
|
||||
|
||||
for (int i = 0; i < num_blobs; ++i) {
|
||||
keys.push_back("key" + std::to_string(i));
|
||||
blobs.push_back("blob" + std::to_string(i));
|
||||
ASSERT_OK(Put(keys[i], blobs[i]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
|
||||
{
|
||||
read_options.fill_cache = false;
|
||||
read_options.read_tier = kReadAllTier;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
int i = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key().ToString(), keys[i]);
|
||||
ASSERT_EQ(iter->value().ToString(), blobs[i]);
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD), 0);
|
||||
}
|
||||
|
||||
{
|
||||
read_options.fill_cache = false;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Try again with no I/O allowed. Since we didn't re-fill the cache,
|
||||
// the blob itself can only be read from the blob file, so iter->Valid()
|
||||
// should be false.
|
||||
iter->SeekToFirst();
|
||||
ASSERT_NOK(iter->status());
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD), 0);
|
||||
}
|
||||
|
||||
{
|
||||
read_options.fill_cache = true;
|
||||
read_options.read_tier = kReadAllTier;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Read blobs from the file and refill the cache.
|
||||
int i = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key().ToString(), keys[i]);
|
||||
ASSERT_EQ(iter->value().ToString(), blobs[i]);
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD),
|
||||
num_blobs);
|
||||
}
|
||||
|
||||
{
|
||||
read_options.fill_cache = false;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Try again with no I/O allowed. The table and the necessary blocks/blobs
|
||||
// should already be in their respective caches.
|
||||
int i = 0;
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key().ToString(), keys[i]);
|
||||
ASSERT_EQ(iter->value().ToString(), blobs[i]);
|
||||
++i;
|
||||
}
|
||||
ASSERT_EQ(i, num_blobs);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, IterateBlobsFromCachePinning) {
|
||||
constexpr size_t min_blob_size = 6;
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
LRUCacheOptions cache_options;
|
||||
cache_options.capacity = 2048;
|
||||
cache_options.num_shard_bits = 0;
|
||||
cache_options.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
|
||||
options.blob_cache = NewLRUCache(cache_options);
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = min_blob_size;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// Put then iterate over three key-values. The second value is below the size
|
||||
// limit and is thus stored inline; the other two are stored separately as
|
||||
// blobs. We expect to have something pinned in the cache iff we are
|
||||
// positioned on a blob.
|
||||
|
||||
constexpr char first_key[] = "first_key";
|
||||
constexpr char first_value[] = "long_value";
|
||||
static_assert(sizeof(first_value) - 1 >= min_blob_size,
|
||||
"first_value too short to be stored as blob");
|
||||
|
||||
ASSERT_OK(Put(first_key, first_value));
|
||||
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char second_value[] = "short";
|
||||
static_assert(sizeof(second_value) - 1 < min_blob_size,
|
||||
"second_value too long to be inlined");
|
||||
|
||||
ASSERT_OK(Put(second_key, 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(Put(third_key, third_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
{
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = true;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), first_key);
|
||||
ASSERT_EQ(iter->value(), first_value);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), second_key);
|
||||
ASSERT_EQ(iter->value(), second_value);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), third_key);
|
||||
ASSERT_EQ(iter->value(), third_value);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
{
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = false;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
|
||||
iter->SeekToFirst();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), first_key);
|
||||
ASSERT_EQ(iter->value(), first_value);
|
||||
ASSERT_GT(options.blob_cache->GetPinnedUsage(), 0);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), second_key);
|
||||
ASSERT_EQ(iter->value(), second_value);
|
||||
ASSERT_EQ(options.blob_cache->GetPinnedUsage(), 0);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), third_key);
|
||||
ASSERT_EQ(iter->value(), third_value);
|
||||
ASSERT_GT(options.blob_cache->GetPinnedUsage(), 0);
|
||||
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(options.blob_cache->GetPinnedUsage(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = false;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
|
||||
iter->SeekToLast();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), third_key);
|
||||
ASSERT_EQ(iter->value(), third_value);
|
||||
ASSERT_GT(options.blob_cache->GetPinnedUsage(), 0);
|
||||
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), second_key);
|
||||
ASSERT_EQ(iter->value(), second_value);
|
||||
ASSERT_EQ(options.blob_cache->GetPinnedUsage(), 0);
|
||||
|
||||
iter->Prev();
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(iter->key(), first_key);
|
||||
ASSERT_EQ(iter->value(), first_value);
|
||||
ASSERT_GT(options.blob_cache->GetPinnedUsage(), 0);
|
||||
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(options.blob_cache->GetPinnedUsage(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobs) {
|
||||
constexpr size_t min_blob_size = 6;
|
||||
|
||||
@@ -449,141 +127,6 @@ TEST_F(DBBlobBasicTest, MultiGetBlobs) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobsFromCache) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 2 << 20; // 2MB
|
||||
co.num_shard_bits = 2;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
auto backing_cache = NewLRUCache(co);
|
||||
|
||||
constexpr size_t min_blob_size = 6;
|
||||
options.min_blob_size = min_blob_size;
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.blob_cache = backing_cache;
|
||||
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.no_block_cache = false;
|
||||
block_based_options.block_cache = backing_cache;
|
||||
block_based_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_based_options));
|
||||
|
||||
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");
|
||||
|
||||
ASSERT_OK(Put(first_key, 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(Put(second_key, 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(Put(third_key, third_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Try again with no I/O allowed. The first (inlined) value should be
|
||||
// successfully read; however, the two blob values could only be read from the
|
||||
// blob file, so for those the read should return Incomplete.
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
{
|
||||
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_TRUE(statuses[1].IsIncomplete());
|
||||
|
||||
ASSERT_TRUE(statuses[2].IsIncomplete());
|
||||
}
|
||||
|
||||
// Fill the cache when reading blobs from the blob file.
|
||||
read_options.read_tier = kReadAllTier;
|
||||
read_options.fill_cache = true;
|
||||
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// Try again with no I/O allowed. All blobs should be successfully read from
|
||||
// the cache.
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, MultiGetWithDirectIO) {
|
||||
Options options = GetDefaultOptions();
|
||||
@@ -777,23 +320,8 @@ TEST_F(DBBlobBasicTest, MultiGetWithDirectIO) {
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobsFromMultipleFiles) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 2 << 20; // 2MB
|
||||
co.num_shard_bits = 2;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
auto backing_cache = NewLRUCache(co);
|
||||
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.blob_cache = backing_cache;
|
||||
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.no_block_cache = false;
|
||||
block_based_options.block_cache = backing_cache;
|
||||
block_based_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_based_options));
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
@@ -819,64 +347,14 @@ TEST_F(DBBlobBasicTest, MultiGetBlobsFromMultipleFiles) {
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
keys[i] = key_strs[i];
|
||||
}
|
||||
std::array<PinnableSlice, kNumKeys> values;
|
||||
std::array<Status, kNumKeys> statuses;
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), kNumKeys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.read_tier = kReadAllTier;
|
||||
read_options.fill_cache = false;
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, kNumKeys> values;
|
||||
std::array<Status, kNumKeys> statuses;
|
||||
db_->MultiGet(read_options, db_->DefaultColumnFamily(), kNumKeys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(value_strs[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, kNumKeys> values;
|
||||
std::array<Status, kNumKeys> statuses;
|
||||
db_->MultiGet(read_options, db_->DefaultColumnFamily(), kNumKeys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_TRUE(statuses[i].IsIncomplete());
|
||||
ASSERT_TRUE(values[i].empty());
|
||||
}
|
||||
}
|
||||
|
||||
read_options.read_tier = kReadAllTier;
|
||||
read_options.fill_cache = true;
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, kNumKeys> values;
|
||||
std::array<Status, kNumKeys> statuses;
|
||||
db_->MultiGet(read_options, db_->DefaultColumnFamily(), kNumKeys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(value_strs[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, kNumKeys> values;
|
||||
std::array<Status, kNumKeys> statuses;
|
||||
db_->MultiGet(read_options, db_->DefaultColumnFamily(), kNumKeys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(value_strs[i], values[i]);
|
||||
}
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(value_strs[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,26 +366,19 @@ TEST_F(DBBlobBasicTest, GetBlob_CorruptIndex) {
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
|
||||
ASSERT_OK(Put(key, blob));
|
||||
// Fake a corrupt blob index.
|
||||
const std::string blob_index("foobar");
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"Version::Get::TamperWithBlobIndex", [](void* arg) {
|
||||
Slice* const blob_index = static_cast<Slice*>(arg);
|
||||
assert(blob_index);
|
||||
assert(!blob_index->empty());
|
||||
blob_index->remove_prefix(1);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsCorruption());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlob_CorruptIndex) {
|
||||
@@ -930,27 +401,17 @@ TEST_F(DBBlobBasicTest, MultiGetBlob_CorruptIndex) {
|
||||
}
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
ASSERT_OK(Put(key, blob));
|
||||
keys[kNumOfKeys] = key;
|
||||
{
|
||||
// Fake a corrupt blob index.
|
||||
const std::string blob_index("foobar");
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
keys[kNumOfKeys] = Slice(static_cast<const char*>(key), sizeof(key) - 1);
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"Version::MultiGet::TamperWithBlobIndex", [&key](void* arg) {
|
||||
KeyContext* const key_context = static_cast<KeyContext*>(arg);
|
||||
assert(key_context);
|
||||
assert(key_context->key);
|
||||
|
||||
if (*(key_context->key) == key) {
|
||||
Slice* const blob_index = key_context->value;
|
||||
assert(blob_index);
|
||||
assert(!blob_index->empty());
|
||||
blob_index->remove_prefix(1);
|
||||
}
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::array<PinnableSlice, kNumOfKeys + 1> values;
|
||||
std::array<Status, kNumOfKeys + 1> statuses;
|
||||
db_->MultiGet(ReadOptions(), dbfull()->DefaultColumnFamily(), kNumOfKeys + 1,
|
||||
@@ -964,9 +425,6 @@ TEST_F(DBBlobBasicTest, MultiGetBlob_CorruptIndex) {
|
||||
ASSERT_TRUE(statuses[i].IsCorruption());
|
||||
}
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlob_ExceedSoftLimit) {
|
||||
@@ -1236,21 +694,18 @@ TEST_F(DBBlobBasicTest, Properties) {
|
||||
constexpr char key3[] = "key3";
|
||||
constexpr size_t key3_size = sizeof(key3) - 1;
|
||||
|
||||
constexpr char blob[] = "00000000000000";
|
||||
constexpr char blob[] = "0000000000";
|
||||
constexpr size_t blob_size = sizeof(blob) - 1;
|
||||
|
||||
constexpr char longer_blob[] = "00000000000000000000";
|
||||
constexpr size_t longer_blob_size = sizeof(longer_blob) - 1;
|
||||
|
||||
ASSERT_OK(Put(key1, blob));
|
||||
ASSERT_OK(Put(key2, longer_blob));
|
||||
ASSERT_OK(Put(key2, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr size_t first_blob_file_expected_size =
|
||||
BlobLogHeader::kSize +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key1_size) + blob_size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key2_size) +
|
||||
longer_blob_size + BlobLogFooter::kSize;
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key2_size) + blob_size +
|
||||
BlobLogFooter::kSize;
|
||||
|
||||
ASSERT_OK(Put(key3, blob));
|
||||
ASSERT_OK(Flush());
|
||||
@@ -1275,14 +730,6 @@ TEST_F(DBBlobBasicTest, Properties) {
|
||||
&live_blob_file_size));
|
||||
ASSERT_EQ(live_blob_file_size, total_expected_size);
|
||||
|
||||
// Total amount of garbage in live blob files
|
||||
{
|
||||
uint64_t live_blob_file_garbage_size = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kLiveBlobFileGarbageSize,
|
||||
&live_blob_file_garbage_size));
|
||||
ASSERT_EQ(live_blob_file_garbage_size, 0);
|
||||
}
|
||||
|
||||
// Total size of all blob files across all versions
|
||||
// Note: this should be the same as above since we only have one
|
||||
// version at this point.
|
||||
@@ -1300,12 +747,7 @@ TEST_F(DBBlobBasicTest, Properties) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
constexpr size_t expected_garbage_size =
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key2_size) +
|
||||
longer_blob_size;
|
||||
|
||||
constexpr double expected_space_amp =
|
||||
static_cast<double>(total_expected_size) /
|
||||
(total_expected_size - expected_garbage_size);
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key2_size) + blob_size;
|
||||
|
||||
// Blob file stats
|
||||
std::string blob_stats;
|
||||
@@ -1315,17 +757,9 @@ TEST_F(DBBlobBasicTest, Properties) {
|
||||
oss << "Number of blob files: 2\nTotal size of blob files: "
|
||||
<< total_expected_size
|
||||
<< "\nTotal size of garbage in blob files: " << expected_garbage_size
|
||||
<< "\nBlob file space amplification: " << expected_space_amp << '\n';
|
||||
<< '\n';
|
||||
|
||||
ASSERT_EQ(blob_stats, oss.str());
|
||||
|
||||
// Total amount of garbage in live blob files
|
||||
{
|
||||
uint64_t live_blob_file_garbage_size = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kLiveBlobFileGarbageSize,
|
||||
&live_blob_file_garbage_size));
|
||||
ASSERT_EQ(live_blob_file_garbage_size, expected_garbage_size);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, PropertiesMultiVersion) {
|
||||
@@ -1582,205 +1016,16 @@ TEST_P(DBBlobBasicIOErrorTest, CompactionFilterReadBlob_IOError) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, WarmCacheWithBlobsDuringFlush) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 1 << 25;
|
||||
co.num_shard_bits = 2;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
auto backing_cache = NewLRUCache(co);
|
||||
|
||||
options.blob_cache = backing_cache;
|
||||
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.no_block_cache = false;
|
||||
block_based_options.block_cache = backing_cache;
|
||||
block_based_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_based_options));
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.enable_blob_garbage_collection = true;
|
||||
options.blob_garbage_collection_age_cutoff = 1.0;
|
||||
options.prepopulate_blob_cache = PrepopulateBlobCache::kFlushOnly;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
constexpr size_t kNumBlobs = 10;
|
||||
constexpr size_t kValueSize = 100;
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
|
||||
for (size_t i = 1; i <= kNumBlobs; i++) {
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Put(std::to_string(i + kNumBlobs), value)); // Add some overlap
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(i * 2, options.statistics->getTickerCount(BLOB_DB_CACHE_ADD));
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(value, Get(std::to_string(i + kNumBlobs)));
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOB_DB_CACHE_MISS));
|
||||
ASSERT_EQ(i * 2, options.statistics->getTickerCount(BLOB_DB_CACHE_HIT));
|
||||
}
|
||||
|
||||
// Verify compaction not counted
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
EXPECT_EQ(kNumBlobs * 2,
|
||||
options.statistics->getTickerCount(BLOB_DB_CACHE_ADD));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, DynamicallyWarmCacheDuringFlush) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 1 << 25;
|
||||
co.num_shard_bits = 2;
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
auto backing_cache = NewLRUCache(co);
|
||||
|
||||
options.blob_cache = backing_cache;
|
||||
|
||||
BlockBasedTableOptions block_based_options;
|
||||
block_based_options.no_block_cache = false;
|
||||
block_based_options.block_cache = backing_cache;
|
||||
block_based_options.cache_index_and_filter_blocks = true;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(block_based_options));
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.enable_blob_garbage_collection = true;
|
||||
options.blob_garbage_collection_age_cutoff = 1.0;
|
||||
options.prepopulate_blob_cache = PrepopulateBlobCache::kFlushOnly;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
constexpr size_t kNumBlobs = 10;
|
||||
constexpr size_t kValueSize = 100;
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
|
||||
for (size_t i = 1; i <= 5; i++) {
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Put(std::to_string(i + kNumBlobs), value)); // Add some overlap
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(2, options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(value, Get(std::to_string(i + kNumBlobs)));
|
||||
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD));
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_MISS));
|
||||
ASSERT_EQ(2, options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_HIT));
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prepopulate_blob_cache", "kDisable"}}));
|
||||
|
||||
for (size_t i = 6; i <= kNumBlobs; i++) {
|
||||
ASSERT_OK(Put(std::to_string(i), value));
|
||||
ASSERT_OK(Put(std::to_string(i + kNumBlobs), value)); // Add some overlap
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(std::to_string(i)));
|
||||
ASSERT_EQ(value, Get(std::to_string(i + kNumBlobs)));
|
||||
ASSERT_EQ(2, options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD));
|
||||
ASSERT_EQ(2,
|
||||
options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_MISS));
|
||||
ASSERT_EQ(0, options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_HIT));
|
||||
}
|
||||
|
||||
// Verify compaction not counted
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
EXPECT_EQ(0, options.statistics->getTickerCount(BLOB_DB_CACHE_ADD));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, WarmCacheWithBlobsSecondary) {
|
||||
CompressedSecondaryCacheOptions secondary_cache_opts;
|
||||
secondary_cache_opts.capacity = 1 << 20;
|
||||
secondary_cache_opts.num_shard_bits = 0;
|
||||
secondary_cache_opts.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
secondary_cache_opts.compression_type = kNoCompression;
|
||||
|
||||
LRUCacheOptions primary_cache_opts;
|
||||
primary_cache_opts.capacity = 1024;
|
||||
primary_cache_opts.num_shard_bits = 0;
|
||||
primary_cache_opts.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
primary_cache_opts.secondary_cache =
|
||||
NewCompressedSecondaryCache(secondary_cache_opts);
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.enable_blob_files = true;
|
||||
options.blob_cache = NewLRUCache(primary_cache_opts);
|
||||
options.prepopulate_blob_cache = PrepopulateBlobCache::kFlushOnly;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Note: only one of the two blobs fit in the primary cache at any given time.
|
||||
constexpr char first_key[] = "foo";
|
||||
constexpr size_t first_blob_size = 512;
|
||||
const std::string first_blob(first_blob_size, 'a');
|
||||
|
||||
constexpr char second_key[] = "bar";
|
||||
constexpr size_t second_blob_size = 768;
|
||||
const std::string second_blob(second_blob_size, 'b');
|
||||
|
||||
// First blob is inserted into primary cache during flush.
|
||||
ASSERT_OK(Put(first_key, first_blob));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD), 1);
|
||||
|
||||
// Second blob is inserted into primary cache during flush,
|
||||
// First blob is evicted but only a dummy handle is inserted into secondary
|
||||
// cache.
|
||||
ASSERT_OK(Put(second_key, second_blob));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_ADD), 1);
|
||||
|
||||
// First blob is inserted into primary cache.
|
||||
// Second blob is evicted but only a dummy handle is inserted into secondary
|
||||
// cache.
|
||||
ASSERT_EQ(Get(first_key), first_blob);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_MISS), 1);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_HIT), 0);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(SECONDARY_CACHE_HITS),
|
||||
0);
|
||||
// Second blob is inserted into primary cache,
|
||||
// First blob is evicted and is inserted into secondary cache.
|
||||
ASSERT_EQ(Get(second_key), second_blob);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_MISS), 1);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_HIT), 0);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(SECONDARY_CACHE_HITS),
|
||||
0);
|
||||
|
||||
// First blob's dummy item is inserted into primary cache b/c of lookup.
|
||||
// Second blob is still in primary cache.
|
||||
ASSERT_EQ(Get(first_key), first_blob);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_MISS), 0);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_HIT), 1);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(SECONDARY_CACHE_HITS),
|
||||
1);
|
||||
|
||||
// First blob's item is inserted into primary cache b/c of lookup.
|
||||
// Second blob is evicted and inserted into secondary cache.
|
||||
ASSERT_EQ(Get(first_key), first_blob);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_MISS), 0);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(BLOB_DB_CACHE_HIT), 1);
|
||||
ASSERT_EQ(options.statistics->getAndResetTickerCount(SECONDARY_CACHE_HITS),
|
||||
1);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
@@ -18,7 +18,7 @@ class DBBlobCompactionTest : public DBTestBase {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const std::vector<InternalStats::CompactionStats>& GetCompactionStats() {
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
@@ -54,26 +54,6 @@ class FilterByKeyLength : public CompactionFilter {
|
||||
size_t length_threshold_;
|
||||
};
|
||||
|
||||
class FilterByValueLength : public CompactionFilter {
|
||||
public:
|
||||
explicit FilterByValueLength(size_t len) : length_threshold_(len) {}
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.by.value.length";
|
||||
}
|
||||
CompactionFilter::Decision FilterV2(
|
||||
int /*level*/, const Slice& /*key*/, ValueType /*value_type*/,
|
||||
const Slice& existing_value, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
if (existing_value.size() < length_threshold_) {
|
||||
return CompactionFilter::Decision::kRemove;
|
||||
}
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t length_threshold_;
|
||||
};
|
||||
|
||||
class BadBlobCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit BadBlobCompactionFilter(std::string prefix,
|
||||
@@ -263,133 +243,6 @@ TEST_F(DBBlobCompactionTest, FilterByKeyLength) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, FilterByValueLength) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 5;
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kValueLength = 5;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new FilterByValueLength(kValueLength));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
|
||||
const std::vector<std::string> short_value_keys = {"a", "e", "j"};
|
||||
constexpr char short_value[] = "val";
|
||||
const std::vector<std::string> long_value_keys = {"b", "f", "k"};
|
||||
constexpr char long_value[] = "valuevalue";
|
||||
|
||||
DestroyAndReopen(options);
|
||||
for (size_t i = 0; i < short_value_keys.size(); ++i) {
|
||||
ASSERT_OK(Put(short_value_keys[i], short_value));
|
||||
}
|
||||
for (size_t i = 0; i < short_value_keys.size(); ++i) {
|
||||
ASSERT_OK(Put(long_value_keys[i], long_value));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
ASSERT_OK(db_->CompactRange(cro, /*begin=*/nullptr, /*end=*/nullptr));
|
||||
std::string value;
|
||||
for (size_t i = 0; i < short_value_keys.size(); ++i) {
|
||||
ASSERT_TRUE(
|
||||
db_->Get(ReadOptions(), short_value_keys[i], &value).IsNotFound());
|
||||
value.clear();
|
||||
}
|
||||
for (size_t i = 0; i < long_value_keys.size(); ++i) {
|
||||
ASSERT_OK(db_->Get(ReadOptions(), long_value_keys[i], &value));
|
||||
ASSERT_EQ(long_value, value);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter decides between kKeep and kRemove based on value;
|
||||
// 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();
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 1000;
|
||||
options.blob_file_starting_level = 5;
|
||||
options.create_if_missing = true;
|
||||
|
||||
// Open DB with fixed-prefix sst-partitioner so that compaction will cut
|
||||
// new table file when encountering a new key whose 1-byte prefix changes.
|
||||
constexpr size_t key_len = 1;
|
||||
options.sst_partitioner_factory =
|
||||
NewSstPartitionerFixedPrefixFactory(key_len);
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
constexpr size_t blob_size = 3000;
|
||||
|
||||
constexpr char first_key[] = "a";
|
||||
const std::string first_blob(blob_size, 'a');
|
||||
ASSERT_OK(Put(first_key, first_blob));
|
||||
|
||||
constexpr char second_key[] = "b";
|
||||
const std::string second_blob(2 * blob_size, 'b');
|
||||
ASSERT_OK(Put(second_key, second_blob));
|
||||
|
||||
constexpr char third_key[] = "d";
|
||||
const std::string third_blob(blob_size, 'd');
|
||||
ASSERT_OK(Put(third_key, third_blob));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr char fourth_key[] = "c";
|
||||
const std::string fourth_blob(blob_size, 'c');
|
||||
ASSERT_OK(Put(fourth_key, fourth_blob));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(0, GetBlobFileNumbers().size());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(/*level=*/0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/1));
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
|
||||
// No blob file should be created since blob_file_starting_level is 5.
|
||||
ASSERT_EQ(0, GetBlobFileNumbers().size());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/0));
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(/*level=*/1));
|
||||
|
||||
{
|
||||
options.blob_file_starting_level = 1;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put(first_key, first_blob));
|
||||
ASSERT_OK(Put(second_key, second_blob));
|
||||
ASSERT_OK(Put(third_key, third_blob));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(fourth_key, fourth_blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(0, GetBlobFileNumbers().size());
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(/*level=*/0));
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/1));
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
// The compaction's output level equals to blob_file_starting_level.
|
||||
ASSERT_EQ(1, GetBlobFileNumbers().size());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(/*level=*/0));
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(/*level=*/1));
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -562,30 +415,16 @@ TEST_F(DBBlobCompactionTest, CorruptedBlobIndex) {
|
||||
new ValueMutationFilter(""));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Mock a corrupted blob index
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
|
||||
ASSERT_OK(Put(key, blob));
|
||||
std::string blob_idx("blob_idx");
|
||||
WriteBatch write_batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&write_batch, 0, key, blob_idx));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &write_batch));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionIterator::InvokeFilterIfNeeded::TamperWithBlobIndex",
|
||||
[](void* arg) {
|
||||
Slice* const blob_index = static_cast<Slice*>(arg);
|
||||
assert(blob_index);
|
||||
assert(!blob_index->empty());
|
||||
blob_index->remove_prefix(1);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsCorruption());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -656,7 +495,7 @@ TEST_F(DBBlobCompactionTest, TrackGarbage) {
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
VersionSet* const versions = dbfull()->GetVersionSet();
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
@@ -673,7 +512,8 @@ TEST_F(DBBlobCompactionTest, TrackGarbage) {
|
||||
ASSERT_EQ(blob_files.size(), 2);
|
||||
|
||||
{
|
||||
const auto& meta = blob_files.front();
|
||||
auto it = blob_files.begin();
|
||||
const auto& meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
constexpr uint64_t first_expected_bytes =
|
||||
@@ -703,7 +543,8 @@ TEST_F(DBBlobCompactionTest, TrackGarbage) {
|
||||
}
|
||||
|
||||
{
|
||||
const auto& meta = blob_files.back();
|
||||
auto it = blob_files.rbegin();
|
||||
const auto& meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
constexpr uint64_t new_first_expected_bytes =
|
||||
@@ -749,162 +590,16 @@ TEST_F(DBBlobCompactionTest, MergeBlobWithBase) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionReadaheadGarbageCollection) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.enable_blob_garbage_collection = true;
|
||||
options.blob_garbage_collection_age_cutoff = 1.0;
|
||||
options.blob_compaction_readahead_size = 1 << 10;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key", "lime"));
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("key", "pie"));
|
||||
ASSERT_OK(Put("foo", "baz"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
size_t num_non_prefetch_reads = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::GetBlob:ReadFromFile",
|
||||
[&num_non_prefetch_reads](void* /* arg */) { ++num_non_prefetch_reads; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_EQ(Get("key"), "pie");
|
||||
ASSERT_EQ(Get("foo"), "baz");
|
||||
ASSERT_EQ(num_non_prefetch_reads, 0);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionReadaheadFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueMutationFilter("pie"));
|
||||
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.blob_compaction_readahead_size = 1 << 10;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key", "lime"));
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
size_t num_non_prefetch_reads = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::GetBlob:ReadFromFile",
|
||||
[&num_non_prefetch_reads](void* /* arg */) { ++num_non_prefetch_reads; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_EQ(Get("key"), "limepie");
|
||||
ASSERT_EQ(Get("foo"), "barpie");
|
||||
ASSERT_EQ(num_non_prefetch_reads, 0);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionReadaheadMerge) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.blob_compaction_readahead_size = 1 << 10;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key", "lime"));
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Merge("key", "pie"));
|
||||
ASSERT_OK(Merge("foo", "baz"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
size_t num_non_prefetch_reads = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::GetBlob:ReadFromFile",
|
||||
[&num_non_prefetch_reads](void* /* arg */) { ++num_non_prefetch_reads; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_EQ(Get("key"), "lime,pie");
|
||||
ASSERT_EQ(Get("foo"), "bar,baz");
|
||||
ASSERT_EQ(num_non_prefetch_reads, 0);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionDoNotFillCache) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.enable_blob_garbage_collection = true;
|
||||
options.blob_garbage_collection_age_cutoff = 1.0;
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
|
||||
LRUCacheOptions cache_options;
|
||||
cache_options.capacity = 1 << 20;
|
||||
cache_options.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
|
||||
options.blob_cache = NewLRUCache(cache_options);
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key", "lime"));
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("key", "pie"));
|
||||
ASSERT_OK(Put("foo", "baz"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
ASSERT_EQ(options.statistics->getTickerCount(BLOB_DB_CACHE_ADD), 0);
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
@@ -74,6 +74,14 @@ TEST_F(DBBlobCorruptionTest, VerifyWholeBlobFileChecksum) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "db/arena_wrapped_db_iter.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/db_test_util.h"
|
||||
@@ -139,39 +138,20 @@ class DBBlobIndexTest : public DBTestBase {
|
||||
}
|
||||
};
|
||||
|
||||
// Note: the following test case pertains to the StackableDB-based BlobDB
|
||||
// implementation. We should be able to write kTypeBlobIndex to memtables and
|
||||
// SST files.
|
||||
// Should be able to write kTypeBlobIndex to memtables and SST files.
|
||||
TEST_F(DBBlobIndexTest, Write) {
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> key_values;
|
||||
|
||||
constexpr size_t num_key_values = 5;
|
||||
|
||||
key_values.reserve(num_key_values);
|
||||
|
||||
for (size_t i = 1; i <= num_key_values; ++i) {
|
||||
std::string key = "key" + std::to_string(i);
|
||||
|
||||
std::string blob_index;
|
||||
BlobIndex::EncodeInlinedTTL(&blob_index, /* expiration */ 9876543210,
|
||||
"blob" + std::to_string(i));
|
||||
|
||||
key_values.emplace_back(std::move(key), std::move(blob_index));
|
||||
}
|
||||
|
||||
for (const auto& key_value : key_values) {
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
std::string index = ToString(i);
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(PutBlobIndex(&batch, key_value.first, key_value.second));
|
||||
ASSERT_OK(PutBlobIndex(&batch, "key" + index, "blob" + index));
|
||||
ASSERT_OK(Write(&batch));
|
||||
}
|
||||
|
||||
MoveDataTo(tier);
|
||||
|
||||
for (const auto& key_value : key_values) {
|
||||
ASSERT_EQ(GetBlobIndex(key_value.first), key_value.second);
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
std::string index = ToString(i);
|
||||
ASSERT_EQ("blob" + index, GetBlobIndex("key" + index));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -184,19 +164,13 @@ TEST_F(DBBlobIndexTest, Write) {
|
||||
// accidentally opening the base DB of a stacked BlobDB and actual corruption
|
||||
// when using the integrated BlobDB.
|
||||
TEST_F(DBBlobIndexTest, Get) {
|
||||
std::string blob_index;
|
||||
BlobIndex::EncodeInlinedTTL(&blob_index, /* expiration */ 9876543210, "blob");
|
||||
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(batch.Put("key", "value"));
|
||||
ASSERT_OK(PutBlobIndex(&batch, "blob_key", blob_index));
|
||||
ASSERT_OK(PutBlobIndex(&batch, "blob_key", "blob_index"));
|
||||
ASSERT_OK(Write(&batch));
|
||||
|
||||
MoveDataTo(tier);
|
||||
|
||||
// Verify normal value
|
||||
bool is_blob_index = false;
|
||||
PinnableSlice value;
|
||||
@@ -204,7 +178,6 @@ TEST_F(DBBlobIndexTest, Get) {
|
||||
ASSERT_EQ("value", GetImpl("key"));
|
||||
ASSERT_EQ("value", GetImpl("key", &is_blob_index));
|
||||
ASSERT_FALSE(is_blob_index);
|
||||
|
||||
// Verify blob index
|
||||
if (tier <= kImmutableMemtables) {
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsNotSupported());
|
||||
@@ -213,7 +186,7 @@ TEST_F(DBBlobIndexTest, Get) {
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsCorruption());
|
||||
ASSERT_EQ("CORRUPTION", GetImpl("blob_key"));
|
||||
}
|
||||
ASSERT_EQ(blob_index, GetImpl("blob_key", &is_blob_index));
|
||||
ASSERT_EQ("blob_index", GetImpl("blob_key", &is_blob_index));
|
||||
ASSERT_TRUE(is_blob_index);
|
||||
}
|
||||
}
|
||||
@@ -223,14 +196,11 @@ TEST_F(DBBlobIndexTest, Get) {
|
||||
// if blob index is updated with a normal value. See the test case above for
|
||||
// more details.
|
||||
TEST_F(DBBlobIndexTest, Updated) {
|
||||
std::string blob_index;
|
||||
BlobIndex::EncodeInlinedTTL(&blob_index, /* expiration */ 9876543210, "blob");
|
||||
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
WriteBatch batch;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(PutBlobIndex(&batch, "key" + std::to_string(i), blob_index));
|
||||
ASSERT_OK(PutBlobIndex(&batch, "key" + ToString(i), "blob_index"));
|
||||
}
|
||||
ASSERT_OK(Write(&batch));
|
||||
// Avoid blob values from being purged.
|
||||
@@ -248,7 +218,7 @@ TEST_F(DBBlobIndexTest, Updated) {
|
||||
ASSERT_OK(dbfull()->DeleteRange(WriteOptions(), cfh(), "key6", "key9"));
|
||||
MoveDataTo(tier);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ(blob_index, GetBlobIndex("key" + std::to_string(i), snapshot));
|
||||
ASSERT_EQ("blob_index", GetBlobIndex("key" + ToString(i), snapshot));
|
||||
}
|
||||
ASSERT_EQ("new_value", Get("key1"));
|
||||
if (tier <= kImmutableMemtables) {
|
||||
@@ -260,9 +230,9 @@ TEST_F(DBBlobIndexTest, Updated) {
|
||||
ASSERT_EQ("NOT_FOUND", Get("key4"));
|
||||
ASSERT_EQ("a,b,c", GetImpl("key5"));
|
||||
for (int i = 6; i < 9; i++) {
|
||||
ASSERT_EQ("NOT_FOUND", Get("key" + std::to_string(i)));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key" + ToString(i)));
|
||||
}
|
||||
ASSERT_EQ(blob_index, GetBlobIndex("key9"));
|
||||
ASSERT_EQ("blob_index", GetBlobIndex("key9"));
|
||||
dbfull()->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
@@ -301,7 +271,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
};
|
||||
|
||||
auto get_value = [&](int index, int version) {
|
||||
return get_key(index) + "_value" + std::to_string(version);
|
||||
return get_key(index) + "_value" + ToString(version);
|
||||
};
|
||||
|
||||
auto check_iterator = [&](Iterator* iterator, Status::Code expected_status,
|
||||
@@ -501,7 +471,7 @@ TEST_F(DBBlobIndexTest, IntegratedBlobIterate) {
|
||||
auto get_key = [](size_t index) { return ("key" + std::to_string(index)); };
|
||||
|
||||
auto get_value = [&](size_t index, size_t version) {
|
||||
return get_key(index) + "_value" + std::to_string(version);
|
||||
return get_key(index) + "_value" + ToString(version);
|
||||
};
|
||||
|
||||
auto check_iterator = [&](Iterator* iterator, Status expected_status,
|
||||
@@ -594,6 +564,14 @@ TEST_F(DBBlobIndexTest, IntegratedBlobIterate) {
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/prefetch_buffer_collection.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
FilePrefetchBuffer* PrefetchBufferCollection::GetOrCreatePrefetchBuffer(
|
||||
uint64_t file_number) {
|
||||
auto& prefetch_buffer = prefetch_buffers_[file_number];
|
||||
if (!prefetch_buffer) {
|
||||
prefetch_buffer.reset(
|
||||
new FilePrefetchBuffer(readahead_size_, readahead_size_));
|
||||
}
|
||||
|
||||
return prefetch_buffer.get();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -1,38 +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).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "file/file_prefetch_buffer.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A class that owns a collection of FilePrefetchBuffers using the file number
|
||||
// as key. Used for implementing compaction readahead for blob files. Designed
|
||||
// to be accessed by a single thread only: every (sub)compaction needs its own
|
||||
// buffers since they are guaranteed to read different blobs from different
|
||||
// positions even when reading the same file.
|
||||
class PrefetchBufferCollection {
|
||||
public:
|
||||
explicit PrefetchBufferCollection(uint64_t readahead_size)
|
||||
: readahead_size_(readahead_size) {
|
||||
assert(readahead_size_ > 0);
|
||||
}
|
||||
|
||||
FilePrefetchBuffer* GetOrCreatePrefetchBuffer(uint64_t file_number);
|
||||
|
||||
private:
|
||||
uint64_t readahead_size_;
|
||||
std::unordered_map<uint64_t, std::unique_ptr<FilePrefetchBuffer>>
|
||||
prefetch_buffers_; // maps file number to prefetch buffer
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Loaded 100 of 1077 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user